Add prettier npm script and run it in *.js files

This commit is contained in:
Luiz Américo 2019-01-07 20:51:06 -03:00
parent 8ee8aa93a1
commit a6af76467c
56 changed files with 60646 additions and 18675 deletions

3
.prettierrc Normal file
View File

@ -0,0 +1,3 @@
{
"singleQuote": true
}

View File

@ -4,47 +4,49 @@ var ace = require('brace');
require('brace/mode/javascript');
require('brace/theme/monokai');
var lorem = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam in suscipit purus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus nec hendrerit felis. Morbi aliquam facilisis risus eu lacinia. Sed eu leo in turpis fringilla hendrerit. Ut nec accumsan nisl. Suspendisse rhoncus nisl posuere tortor tempus et dapibus elit porta. Cras leo neque, elementum a rhoncus ut, vestibulum non nibh. Phasellus pretium justo turpis. Etiam vulputate, odio vitae tincidunt ultricies, eros odio dapibus nisi, ut tincidunt lacus arcu eu elit. Aenean velit erat, vehicula eget lacinia ut, dignissim non tellus. Aliquam nec lacus mi, sed vestibulum nunc. Suspendisse potenti. Curabitur vitae sem turpis. Vestibulum sed neque eget dolor dapibus porttitor at sit amet sem. Fusce a turpis lorem. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;\nMauris at ante tellus. Vestibulum a metus lectus. Praesent tempor purus a lacus blandit eget gravida ante hendrerit. Cras et eros metus. Sed commodo malesuada eros, vitae interdum augue semper quis. Fusce id magna nunc. Curabitur sollicitudin placerat semper. Cras et mi neque, a dignissim risus. Nulla venenatis porta lacus, vel rhoncus lectus tempor vitae. Duis sagittis venenatis rutrum. Curabitur tempor massa tortor.';
var lorem =
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam in suscipit purus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus nec hendrerit felis. Morbi aliquam facilisis risus eu lacinia. Sed eu leo in turpis fringilla hendrerit. Ut nec accumsan nisl. Suspendisse rhoncus nisl posuere tortor tempus et dapibus elit porta. Cras leo neque, elementum a rhoncus ut, vestibulum non nibh. Phasellus pretium justo turpis. Etiam vulputate, odio vitae tincidunt ultricies, eros odio dapibus nisi, ut tincidunt lacus arcu eu elit. Aenean velit erat, vehicula eget lacinia ut, dignissim non tellus. Aliquam nec lacus mi, sed vestibulum nunc. Suspendisse potenti. Curabitur vitae sem turpis. Vestibulum sed neque eget dolor dapibus porttitor at sit amet sem. Fusce a turpis lorem. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;\nMauris at ante tellus. Vestibulum a metus lectus. Praesent tempor purus a lacus blandit eget gravida ante hendrerit. Cras et eros metus. Sed commodo malesuada eros, vitae interdum augue semper quis. Fusce id magna nunc. Curabitur sollicitudin placerat semper. Cras et mi neque, a dignissim risus. Nulla venenatis porta lacus, vel rhoncus lectus tempor vitae. Duis sagittis venenatis rutrum. Curabitur tempor massa tortor.';
function makePDF(PDFDocument, blobStream, lorem, iframe) {
// create a document and pipe to a blob
var doc = new PDFDocument();
var stream = doc.pipe(blobStream());
// draw some text
doc.fontSize(25)
.text('Here is some vector graphics...', 100, 80);
doc.fontSize(25).text('Here is some vector graphics...', 100, 80);
// some vector graphics
doc.save()
.moveTo(100, 150)
.lineTo(100, 250)
.lineTo(200, 250)
.fill("#FF3300");
doc.circle(280, 200, 50)
.fill("#6600FF");
doc
.save()
.moveTo(100, 150)
.lineTo(100, 250)
.lineTo(200, 250)
.fill('#FF3300');
doc.circle(280, 200, 50).fill('#6600FF');
// an SVG path
doc.scale(0.6)
.translate(470, 130)
.path('M 250,75 L 323,301 131,161 369,161 177,301 z')
.fill('red', 'even-odd')
.restore();
doc
.scale(0.6)
.translate(470, 130)
.path('M 250,75 L 323,301 131,161 369,161 177,301 z')
.fill('red', 'even-odd')
.restore();
// and some justified text wrapped into columns
doc.text('And here is some wrapped text...', 100, 300)
.font('Times-Roman', 13)
.moveDown()
.text(lorem, {
width: 412,
align: 'justify',
indent: 30,
columns: 2,
height: 300,
ellipsis: true
});
doc
.text('And here is some wrapped text...', 100, 300)
.font('Times-Roman', 13)
.moveDown()
.text(lorem, {
width: 412,
align: 'justify',
indent: 30,
columns: 2,
height: 300,
ellipsis: true
});
// end and display the document in the iframe to the right
doc.end();
stream.on('finish', function() {
@ -58,19 +60,30 @@ editor.getSession().setMode('ace/mode/javascript');
editor.setValue(
makePDF
.toString()
.split('\n').slice(1, -1).join('\n')
.replace(/^ /mg, '')
.split('\n')
.slice(1, -1)
.join('\n')
.replace(/^ /gm, '')
);
editor.getSession().getSelection().clearSelection();
editor
.getSession()
.getSelection()
.clearSelection();
var iframe = document.querySelector('iframe');
makePDF(PDFDocument, blobStream, lorem, iframe);
editor.getSession().on('change', function() {
try {
var fn = new Function("PDFDocument", "blobStream", "lorem", "iframe", editor.getValue());
var fn = new Function(
'PDFDocument',
'blobStream',
'lorem',
'iframe',
editor.getValue()
);
fn(PDFDocument, blobStream, lorem, iframe);
} catch (e) {
console.log(e)
};
console.log(e);
}
});

View File

@ -4,10 +4,7 @@ const fs = require('fs');
const doc = new PDFDocument();
// files with alpha channel -> uses zlib.deflate
const files = [
'test.png',
'test3.png'
];
const files = ['test.png', 'test3.png'];
const filesData = files.map(fileName => {
return fs.readFileSync(`images/${fileName}`);
@ -15,19 +12,19 @@ const filesData = files.map(fileName => {
const iterationCount = 100;
console.time('png-bench')
console.time('png-bench');
for (let i = 0; i < iterationCount; i++) {
for (let i = 0; i < iterationCount; i++) {
filesData.forEach(data => {
doc.image(data)
doc.addPage()
})
doc.image(data);
doc.addPage();
});
}
doc.on('data', () => {})
doc.on('data', () => {});
doc.on('end', () => {
doc.on('end', () => {
console.timeEnd('png-bench');
});
doc.end();
doc.end();

View File

@ -3,7 +3,7 @@ var tiger = require('./tiger');
var fs = require('fs');
// Create a new PDFDocument
var doc = new PDFDocument;
var doc = new PDFDocument();
doc.pipe(fs.createWriteStream('out.pdf'));
@ -16,34 +16,64 @@ doc.info['Author'] = 'Devon Govett';
doc.registerFont('Palatino', 'fonts/PalatinoBold.ttf');
// Set the font, draw some text, and embed an image
doc.font('Palatino').fontSize(25).text('Some text with an embedded font!', 100, 100).fontSize(18).text('PNG and JPEG images:').image('images/test.png', 100, 160, {
width: 412
}).image('images/test.jpeg', 190, 400, {
height: 300
});
doc
.font('Palatino')
.fontSize(25)
.text('Some text with an embedded font!', 100, 100)
.fontSize(18)
.text('PNG and JPEG images:')
.image('images/test.png', 100, 160, {
width: 412
})
.image('images/test.jpeg', 190, 400, {
height: 300
});
// Add another page
doc.addPage().fontSize(25).text('Here is some vector graphics...', 100, 100);
doc
.addPage()
.fontSize(25)
.text('Here is some vector graphics...', 100, 100);
// Draw a triangle and a circle
doc.save().moveTo(100, 150).lineTo(100, 250).lineTo(200, 250).fill("#FF3300");
doc
.save()
.moveTo(100, 150)
.lineTo(100, 250)
.lineTo(200, 250)
.fill('#FF3300');
doc.circle(280, 200, 50).fill("#6600FF");
doc.circle(280, 200, 50).fill('#6600FF');
doc.scale(0.6).translate(470, -380).path('M 250,75 L 323,301 131,161 369,161 177,301 z').fill('red', 'even-odd').restore(); // render an SVG path // fill using the even-odd winding rule
doc
.scale(0.6)
.translate(470, -380)
.path('M 250,75 L 323,301 131,161 369,161 177,301 z')
.fill('red', 'even-odd')
.restore(); // render an SVG path // fill using the even-odd winding rule
var loremIpsum = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam in suscipit purus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus nec hendrerit felis. Morbi aliquam facilisis risus eu lacinia. Sed eu leo in turpis fringilla hendrerit. Ut nec accumsan nisl. Suspendisse rhoncus nisl posuere tortor tempus et dapibus elit porta. Cras leo neque, elementum a rhoncus ut, vestibulum non nibh. Phasellus pretium justo turpis. Etiam vulputate, odio vitae tincidunt ultricies, eros odio dapibus nisi, ut tincidunt lacus arcu eu elit. Aenean velit erat, vehicula eget lacinia ut, dignissim non tellus. Aliquam nec lacus mi, sed vestibulum nunc. Suspendisse potenti. Curabitur vitae sem turpis. Vestibulum sed neque eget dolor dapibus porttitor at sit amet sem. Fusce a turpis lorem. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;\nMauris at ante tellus. Vestibulum a metus lectus. Praesent tempor purus a lacus blandit eget gravida ante hendrerit. Cras et eros metus. Sed commodo malesuada eros, vitae interdum augue semper quis. Fusce id magna nunc. Curabitur sollicitudin placerat semper. Cras et mi neque, a dignissim risus. Nulla venenatis porta lacus, vel rhoncus lectus tempor vitae. Duis sagittis venenatis rutrum. Curabitur tempor massa tortor.';
var loremIpsum =
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam in suscipit purus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus nec hendrerit felis. Morbi aliquam facilisis risus eu lacinia. Sed eu leo in turpis fringilla hendrerit. Ut nec accumsan nisl. Suspendisse rhoncus nisl posuere tortor tempus et dapibus elit porta. Cras leo neque, elementum a rhoncus ut, vestibulum non nibh. Phasellus pretium justo turpis. Etiam vulputate, odio vitae tincidunt ultricies, eros odio dapibus nisi, ut tincidunt lacus arcu eu elit. Aenean velit erat, vehicula eget lacinia ut, dignissim non tellus. Aliquam nec lacus mi, sed vestibulum nunc. Suspendisse potenti. Curabitur vitae sem turpis. Vestibulum sed neque eget dolor dapibus porttitor at sit amet sem. Fusce a turpis lorem. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;\nMauris at ante tellus. Vestibulum a metus lectus. Praesent tempor purus a lacus blandit eget gravida ante hendrerit. Cras et eros metus. Sed commodo malesuada eros, vitae interdum augue semper quis. Fusce id magna nunc. Curabitur sollicitudin placerat semper. Cras et mi neque, a dignissim risus. Nulla venenatis porta lacus, vel rhoncus lectus tempor vitae. Duis sagittis venenatis rutrum. Curabitur tempor massa tortor.';
// Draw some text wrapped to 412 points wide
doc.text('And here is some wrapped text...', 100, 300).font('Helvetica', 13).moveDown().text(loremIpsum, { // move down 1 line
width: 412,
align: 'justify',
indent: 30,
paragraphGap: 5
});
doc
.text('And here is some wrapped text...', 100, 300)
.font('Helvetica', 13)
.moveDown()
.text(loremIpsum, {
// move down 1 line
width: 412,
align: 'justify',
indent: 30,
paragraphGap: 5
});
// Add another page, and set the font back
doc.addPage().font('Palatino', 25).text('Rendering some SVG paths...', 100, 100).translate(220, 300);
// Add another page, and set the font back
doc
.addPage()
.font('Palatino', 25)
.text('Rendering some SVG paths...', 100, 100)
.translate(220, 300);
var i, len, part;
// Render each path that makes up the tiger image
@ -67,14 +97,19 @@ for (i = 0, len = tiger.length; i < len; i++) {
doc.restore();
}
// Add some text with annotations
doc.addPage().fillColor("blue").text('Here is a link!', 100, 100, {
link: 'http://google.com/',
underline: true
});
// Add some text with annotations
doc
.addPage()
.fillColor('blue')
.text('Here is a link!', 100, 100, {
link: 'http://google.com/',
underline: true
});
// Add a list with a font loaded from a TrueType collection file
doc.fillColor('#000').font('fonts/Chalkboard.ttc', 'Chalkboard', 16).list(['One', 'Two', 'Three'], 100, 150);
// Add a list with a font loaded from a TrueType collection file
doc
.fillColor('#000')
.font('fonts/Chalkboard.ttc', 'Chalkboard', 16)
.list(['One', 'Two', 'Three'], 100, 150);
doc.end();

File diff suppressed because one or more lines are too long

View File

@ -17,7 +17,7 @@ process.chdir(__dirname);
// setup code mirror javascript mode
const filename = require.resolve('codemirror/mode/javascript/javascript');
const jsMode = fs.readFileSync(filename, 'utf8');
vm.runInNewContext(jsMode, {CodeMirror});
vm.runInNewContext(jsMode, { CodeMirror });
// style definitions for markdown
const styles = {
@ -71,7 +71,7 @@ const styles = {
padding: 10
}
};
// syntax highlighting colors
// based on Github's theme
const colors = {
@ -101,13 +101,14 @@ const colors = {
};
// shared lorem ipsum text so we don't need to copy it into every example
const lorem = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam in suscipit purus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus nec hendrerit felis. Morbi aliquam facilisis risus eu lacinia. Sed eu leo in turpis fringilla hendrerit. Ut nec accumsan nisl. Suspendisse rhoncus nisl posuere tortor tempus et dapibus elit porta. Cras leo neque, elementum a rhoncus ut, vestibulum non nibh. Phasellus pretium justo turpis. Etiam vulputate, odio vitae tincidunt ultricies, eros odio dapibus nisi, ut tincidunt lacus arcu eu elit. Aenean velit erat, vehicula eget lacinia ut, dignissim non tellus. Aliquam nec lacus mi, sed vestibulum nunc. Suspendisse potenti. Curabitur vitae sem turpis. Vestibulum sed neque eget dolor dapibus porttitor at sit amet sem. Fusce a turpis lorem. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;';
const lorem =
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam in suscipit purus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus nec hendrerit felis. Morbi aliquam facilisis risus eu lacinia. Sed eu leo in turpis fringilla hendrerit. Ut nec accumsan nisl. Suspendisse rhoncus nisl posuere tortor tempus et dapibus elit porta. Cras leo neque, elementum a rhoncus ut, vestibulum non nibh. Phasellus pretium justo turpis. Etiam vulputate, odio vitae tincidunt ultricies, eros odio dapibus nisi, ut tincidunt lacus arcu eu elit. Aenean velit erat, vehicula eget lacinia ut, dignissim non tellus. Aliquam nec lacus mi, sed vestibulum nunc. Suspendisse potenti. Curabitur vitae sem turpis. Vestibulum sed neque eget dolor dapibus porttitor at sit amet sem. Fusce a turpis lorem. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;';
let codeBlocks = [];
let lastType = null;
// This class represents a node in the markdown tree, and can render it to pdf
class Node {
class Node {
constructor(tree) {
// special case for text nodes
if (typeof tree === 'string') {
@ -115,14 +116,14 @@ class Node {
this.text = tree;
return;
}
this.type = tree.shift();
this.attrs = {};
if ((typeof tree[0] === 'object') && !Array.isArray(tree[0])) {
if (typeof tree[0] === 'object' && !Array.isArray(tree[0])) {
this.attrs = tree.shift();
}
// parse sub nodes
this.content = (() => {
const result = [];
@ -131,12 +132,12 @@ class Node {
}
return result;
})();
switch (this.type) {
case 'header':
this.type = `h${this.attrs.level}`;
break;
case 'code_block':
// use code mirror to syntax highlight the code block
var code = this.content[0].text;
@ -147,75 +148,83 @@ class Node {
color,
continued: text !== '\n'
};
return this.content.push(new Node(['code', opts, text]));
});
__guard__(this.content[this.content.length - 1], x => x.attrs.continued = false);
});
__guard__(
this.content[this.content.length - 1],
x => (x.attrs.continued = false)
);
codeBlocks.push(code);
break;
case 'img':
// images are used to generate inline example output
// stores the JS so it can be run
// stores the JS so it can be run
// in the render method
this.type = 'example';
code = codeBlocks[this.attrs.alt];
if (code) { this.code = code; }
if (code) {
this.code = code;
}
this.height = +this.attrs.title || 0;
break;
}
this.style = styles[this.type] || styles.para;
}
// sets the styles on the document for this node
setStyle(doc) {
if (this.style.font) {
doc.font(this.style.font);
}
if (this.style.fontSize) {
doc.fontSize(this.style.fontSize);
}
if (this.style.color || this.attrs.color) {
doc.fillColor(this.style.color || this.attrs.color);
} else {
doc.fillColor('black');
}
const options = {};
options.align = this.style.align;
options.link = this.attrs.href || false; // override continued link
if (this.attrs.continued != null) { options.continued = this.attrs.continued; }
options.link = this.attrs.href || false; // override continued link
if (this.attrs.continued != null) {
options.continued = this.attrs.continued;
}
return options;
}
// renders this node and its subnodes to the document
render(doc, continued) {
let y;
if (continued == null) { continued = false; }
if (continued == null) {
continued = false;
}
switch (this.type) {
case 'example':
this.setStyle(doc);
// translate all points in the example code to
// translate all points in the example code to
// the current point in the document
doc.moveDown();
doc.save();
doc.translate(doc.x, doc.y);
var { x } = doc;
({ y } = doc);
doc.x = (doc.y = 0);
doc.x = doc.y = 0;
// run the example code with the document
vm.runInNewContext(this.code, {
doc,
lorem
}
);
});
// restore points and styles
y += doc.y;
doc.restore();
@ -231,36 +240,45 @@ class Node {
const fragment = this.content[index];
if (fragment.type === 'text') {
// add a new page for each heading, unless it follows another heading
if (['h1', 'h2'].includes(this.type) && (lastType != null) && (lastType !== 'h1')) {
if (
['h1', 'h2'].includes(this.type) &&
lastType != null &&
lastType !== 'h1'
) {
doc.addPage();
}
if (this.type === 'h1') {
doc.h1Outline = doc.outline.addItem(fragment.text);
} else if ((this.type === 'h2') && (doc.h1Outline !== null)) {
} else if (this.type === 'h2' && doc.h1Outline !== null) {
doc.h1Outline.addItem(fragment.text);
}
// set styles and whether this fragment is continued (for rich text wrapping)
const options = this.setStyle(doc);
if (options.continued == null) { options.continued = continued || (index < (this.content.length - 1)); }
if (options.continued == null) {
options.continued = continued || index < this.content.length - 1;
}
// remove newlines unless this is code
if (this.type !== 'code') {
fragment.text = fragment.text.replace(/[\r\n]\s*/g, ' ');
}
doc.text(fragment.text, options);
} else {
fragment.render(doc, (index < (this.content.length - 1)) && (this.type !== 'bulletlist'));
fragment.render(
doc,
index < this.content.length - 1 && this.type !== 'bulletlist'
);
}
lastType = this.type;
}
}
if (this.style.padding) {
return doc.y += this.style.padding;
return (doc.y += this.style.padding);
}
}
}
@ -270,7 +288,7 @@ const render = function(doc, filename) {
codeBlocks = [];
const tree = md.parse(fs.readFileSync(filename, 'utf8'));
tree.shift();
return (() => {
const result = [];
while (tree.length) {
@ -281,39 +299,37 @@ const render = function(doc, filename) {
})();
};
// renders the title page of the guide
// renders the title page of the guide
const renderTitlePage = function(doc) {
const title = 'PDFKit Guide';
const author = 'By Devon Govett';
const version = `Version ${require('../package.json').version}`;
doc.font('fonts/AlegreyaSans-Light.ttf', 60);
doc.y = (doc.page.height / 2) - doc.currentLineHeight();
doc.text(title, {align: 'center'});
doc.y = doc.page.height / 2 - doc.currentLineHeight();
doc.text(title, { align: 'center' });
const w = doc.widthOfString(title);
doc.h1Outline = doc.outline.addItem(title);
doc.fontSize(20);
doc.y -= 10;
doc.text(author, {
align: 'center',
indent: w - doc.widthOfString(author)
}
);
});
doc.font(styles.para.font, 10);
doc.text(version, {
align: 'center',
indent: w - doc.widthOfString(version)
}
);
});
return doc.addPage();
};
// render all sections of the guide and write the pdf file
(function() {
const doc = new PDFDocument;
const doc = new PDFDocument();
doc.pipe(fs.createWriteStream('guide.pdf'));
renderTitlePage(doc);
render(doc, 'getting_started.md');
@ -326,5 +342,7 @@ const renderTitlePage = function(doc) {
})();
function __guard__(value, transform) {
return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined;
}
return typeof value !== 'undefined' && value !== null
? transform(value)
: undefined;
}

View File

@ -2,7 +2,7 @@ const jade = require('jade');
const { markdown } = require('markdown');
const fs = require('fs');
const vm = require('vm');
const {exec} = require('child_process');
const { exec } = require('child_process');
const PDFDocument = require('../');
process.chdir(__dirname);
@ -21,27 +21,31 @@ const files = [
];
// shared lorem ipsum text so we don't need to copy it into every example
const lorem = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam in suscipit purus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus nec hendrerit felis. Morbi aliquam facilisis risus eu lacinia. Sed eu leo in turpis fringilla hendrerit. Ut nec accumsan nisl. Suspendisse rhoncus nisl posuere tortor tempus et dapibus elit porta. Cras leo neque, elementum a rhoncus ut, vestibulum non nibh. Phasellus pretium justo turpis. Etiam vulputate, odio vitae tincidunt ultricies, eros odio dapibus nisi, ut tincidunt lacus arcu eu elit. Aenean velit erat, vehicula eget lacinia ut, dignissim non tellus. Aliquam nec lacus mi, sed vestibulum nunc. Suspendisse potenti. Curabitur vitae sem turpis. Vestibulum sed neque eget dolor dapibus porttitor at sit amet sem. Fusce a turpis lorem. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;';
const lorem =
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam in suscipit purus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus nec hendrerit felis. Morbi aliquam facilisis risus eu lacinia. Sed eu leo in turpis fringilla hendrerit. Ut nec accumsan nisl. Suspendisse rhoncus nisl posuere tortor tempus et dapibus elit porta. Cras leo neque, elementum a rhoncus ut, vestibulum non nibh. Phasellus pretium justo turpis. Etiam vulputate, odio vitae tincidunt ultricies, eros odio dapibus nisi, ut tincidunt lacus arcu eu elit. Aenean velit erat, vehicula eget lacinia ut, dignissim non tellus. Aliquam nec lacus mi, sed vestibulum nunc. Suspendisse potenti. Curabitur vitae sem turpis. Vestibulum sed neque eget dolor dapibus porttitor at sit amet sem. Fusce a turpis lorem. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;';
const extractHeaders = function(tree) {
const headers = [];
for (let index = 0; index < tree.length; index++) {
const node = tree[index];
if ((node[0] === 'header') && ((headers.length === 0) || (node[1].level > 1))) {
if (node[1].level > 2) { node[1].level = 2; }
if (node[0] === 'header' && (headers.length === 0 || node[1].level > 1)) {
if (node[1].level > 2) {
node[1].level = 2;
}
const hash = node[2].toLowerCase().replace(/\s+/g, '_');
node[1].id = hash;
headers.push({
headers.push({
hash,
title: node[2]});
title: node[2]
});
}
}
return headers;
};
let imageIndex = 0;
let imageIndex = 0;
const generateImages = function(tree) {
// find code blocks
const codeBlocks = [];
@ -50,43 +54,42 @@ const generateImages = function(tree) {
codeBlocks.push(node[1]);
}
}
for (node of tree) {
if ((node[0] === 'para') && Array.isArray(node[1]) && (node[1][0] === 'img')) {
if (node[0] === 'para' && Array.isArray(node[1]) && node[1][0] === 'img') {
// compile the code
const attrs = node[1][1];
let code = codeBlocks[attrs.alt];
let code = codeBlocks[attrs.alt];
delete attrs.height; // used for pdf generation
// create a PDF and run the example
const doc = new PDFDocument;
const doc = new PDFDocument();
const f = `img/${imageIndex++}`;
var file = fs.createWriteStream(`${f}.pdf`);
doc.pipe(file);
doc.translate(doc.x, doc.y);
doc.scale(0.8);
doc.x = (doc.y = 0);
doc.x = doc.y = 0;
vm.runInNewContext(code, {
doc,
lorem
}
);
});
delete attrs.title;
delete attrs.alt;
attrs.href = `${f}.png`;
// write the PDF, convert to PNG using the mac `sips`
// command line tool, and trim with graphicsmagick
file.on('finish', () =>
exec(`sips -s format png ${f}.pdf --out ${f}.png`, function() {
fs.unlink(`${f}.pdf`);
exec(`gm convert ${f}.png -trim ${f}.png`);
})
)
);
}
}
};
@ -94,18 +97,19 @@ const generateImages = function(tree) {
const pages = [];
for (let file of Array.from(files)) {
let content = fs.readFileSync(file, 'utf8');
// turn github highlighted code blocks into normal markdown code blocks
content = content.replace(/^```javascript\n((:?.|\n)*?)\n```/mg, (m, $1) => ` ${$1.split('\n').join('\n ')}`);
content = content.replace(
/^```javascript\n((:?.|\n)*?)\n```/gm,
(m, $1) => ` ${$1.split('\n').join('\n ')}`
);
const tree = markdown.parse(content);
const headers = extractHeaders(tree);
generateImages(tree);
file = file
.replace(/README\.md/, 'index')
.replace(/\.md$/, '');
file = file.replace(/README\.md/, 'index').replace(/\.md$/, '');
pages.push({
file,
url: `/docs/${file}.html`,

View File

@ -7,5 +7,5 @@ class PDFAbstractReference {
throw new Error('Must be implemented by subclasses');
}
}
export default PDFAbstractReference;

View File

@ -1,31 +1,33 @@
class Data {
class Data {
constructor(data) {
if (data == null) { data = []; }
if (data == null) {
data = [];
}
this.data = data;
this.pos = 0;
this.length = this.data.length;
}
readByte() {
return this.data[this.pos++];
}
writeByte(byte) {
return this.data[this.pos++] = byte;
return (this.data[this.pos++] = byte);
}
byteAt(index) {
return this.data[index];
}
readBool() {
return !!this.readByte();
}
writeBool(val) {
return this.writeByte(val ? 1 : 0);
}
readUInt32() {
const b1 = this.readByte() * 0x1000000;
const b2 = this.readByte() << 16;
@ -33,54 +35,70 @@ class Data {
const b4 = this.readByte();
return b1 + b2 + b3 + b4;
}
writeUInt32(val) {
this.writeByte((val >>> 24) & 0xff);
this.writeByte((val >> 16) & 0xff);
this.writeByte((val >> 8) & 0xff);
return this.writeByte(val & 0xff);
}
readInt32() {
const int = this.readUInt32();
if (int >= 0x80000000) { return int - 0x100000000; } else { return int; }
if (int >= 0x80000000) {
return int - 0x100000000;
} else {
return int;
}
}
writeInt32(val) {
if (val < 0) { val += 0x100000000; }
if (val < 0) {
val += 0x100000000;
}
return this.writeUInt32(val);
}
readUInt16() {
const b1 = this.readByte() << 8;
const b2 = this.readByte();
return b1 | b2;
}
writeUInt16(val) {
this.writeByte((val >> 8) & 0xff);
return this.writeByte(val & 0xff);
}
readInt16() {
const int = this.readUInt16();
if (int >= 0x8000) { return int - 0x10000; } else { return int; }
if (int >= 0x8000) {
return int - 0x10000;
} else {
return int;
}
}
writeInt16(val) {
if (val < 0) { val += 0x10000; }
if (val < 0) {
val += 0x10000;
}
return this.writeUInt16(val);
}
readString(length) {
const ret = [];
for (let i = 0, end = length, asc = 0 <= end; asc ? i < end : i > end; asc ? i++ : i--) {
for (
let i = 0, end = length, asc = 0 <= end;
asc ? i < end : i > end;
asc ? i++ : i--
) {
ret[i] = String.fromCharCode(this.readByte());
}
return ret.join('');
}
writeString(val) {
// todo: remove returning data. Seems not used
const result = [];
@ -89,20 +107,20 @@ class Data {
}
return result;
}
stringAt(pos, length) {
this.pos = pos;
return this.readString(length);
}
readShort() {
return this.readInt16();
}
writeShort(val) {
return this.writeInt16(val);
}
readLongLong() {
const b1 = this.readByte();
const b2 = this.readByte();
@ -112,28 +130,35 @@ class Data {
const b6 = this.readByte();
const b7 = this.readByte();
const b8 = this.readByte();
if (b1 & 0x80) { // sign -> avoid overflow
return (((b1 ^ 0xff) * 0x100000000000000) +
((b2 ^ 0xff) * 0x1000000000000) +
((b3 ^ 0xff) * 0x10000000000) +
((b4 ^ 0xff) * 0x100000000) +
((b5 ^ 0xff) * 0x1000000) +
((b6 ^ 0xff) * 0x10000) +
((b7 ^ 0xff) * 0x100) +
(b8 ^ 0xff) + 1) * -1;
if (b1 & 0x80) {
// sign -> avoid overflow
return (
((b1 ^ 0xff) * 0x100000000000000 +
(b2 ^ 0xff) * 0x1000000000000 +
(b3 ^ 0xff) * 0x10000000000 +
(b4 ^ 0xff) * 0x100000000 +
(b5 ^ 0xff) * 0x1000000 +
(b6 ^ 0xff) * 0x10000 +
(b7 ^ 0xff) * 0x100 +
(b8 ^ 0xff) +
1) *
-1
);
}
return (b1 * 0x100000000000000) +
(b2 * 0x1000000000000) +
(b3 * 0x10000000000) +
(b4 * 0x100000000) +
(b5 * 0x1000000) +
(b6 * 0x10000) +
(b7 * 0x100) +
b8;
return (
b1 * 0x100000000000000 +
b2 * 0x1000000000000 +
b3 * 0x10000000000 +
b4 * 0x100000000 +
b5 * 0x1000000 +
b6 * 0x10000 +
b7 * 0x100 +
b8
);
}
writeLongLong(val) {
const high = Math.floor(val / 0x100000000);
const low = val & 0xffffffff;
@ -146,32 +171,35 @@ class Data {
this.writeByte((low >> 8) & 0xff);
return this.writeByte(low & 0xff);
}
readInt() {
return this.readInt32();
}
writeInt(val) {
return this.writeInt32(val);
}
slice(start, end) {
return this.data.slice(start, end);
}
read(bytes) {
const buf = [];
for (let i = 0, end = bytes, asc = 0 <= end; asc ? i < end : i > end; asc ? i++ : i--) {
for (
let i = 0, end = bytes, asc = 0 <= end;
asc ? i < end : i > end;
asc ? i++ : i--
) {
buf.push(this.readByte());
}
return buf;
}
write(bytes) {
return bytes.map((byte) =>
this.writeByte(byte));
return bytes.map(byte => this.writeByte(byte));
}
}
export default Data;
export default Data;

View File

@ -43,7 +43,8 @@ class PDFDocument extends stream.Readable {
}
// Whether streams should be compressed
this.compress = this.options.compress != null ? this.options.compress : true;
this.compress =
this.options.compress != null ? this.options.compress : true;
this._pageBuffer = [];
this._pageBufferStart = 0;
@ -56,15 +57,20 @@ class PDFDocument extends stream.Readable {
const Pages = this.ref({
Type: 'Pages',
Count: 0,
Kids: []});
Kids: []
});
Pages.finalize = function() {
this.offset = this.document._offset;
this.document._write(this.id + " " + this.gen + " obj");
this.document._write(this.id + ' ' + this.gen + ' obj');
this.document._write('<<');
this.document._write('/Type /Pages');
this.document._write(`/Count ${this.data.Count}`);
this.document._write(`/Kids [${Buffer.concat(this.data.Kids).slice(0,-1).toString()}]`);
this.document._write(
`/Kids [${Buffer.concat(this.data.Kids)
.slice(0, -1)
.toString()}]`
);
this.document._write('>>');
this.document._write('endobj');
return this.document._refEnd(this);
@ -111,7 +117,7 @@ class PDFDocument extends stream.Readable {
this._write(`%PDF-${this.version}`);
// 4 binary chars, as recommended by the spec
this._write("%\xFF\xFF\xFF\xFF");
this._write('%\xFF\xFF\xFF\xFF');
// Add the first page
if (this.options.autoFirstPage !== false) {
@ -121,8 +127,12 @@ class PDFDocument extends stream.Readable {
addPage(options) {
// end the current page if needed
if (options == null) { ({ options } = this); }
if (!this.options.bufferPages) { this.flushPages(); }
if (options == null) {
({ options } = this);
}
if (!this.options.bufferPages) {
this.flushPages();
}
// create a page object
this.page = new PDFPage(this, options);
@ -154,10 +164,14 @@ class PDFDocument extends stream.Readable {
switchToPage(n) {
let page;
if (!(page = this._pageBuffer[n - this._pageBufferStart])) {
throw new Error(`switchToPage(${n}) out of bounds, current buffer covers pages ${this._pageBufferStart} to ${(this._pageBufferStart + this._pageBuffer.length) - 1}`);
throw new Error(
`switchToPage(${n}) out of bounds, current buffer covers pages ${
this._pageBufferStart
} to ${this._pageBufferStart + this._pageBuffer.length - 1}`
);
}
return this.page = page;
return (this.page = page);
}
flushPages() {
@ -169,7 +183,6 @@ class PDFDocument extends stream.Readable {
for (let page of pages) {
page.end();
}
}
ref(data) {
@ -180,7 +193,7 @@ class PDFDocument extends stream.Readable {
}
_read() {}
// do nothing, but this method is required by node
// do nothing, but this method is required by node
_write(data) {
if (!Buffer.isBuffer(data)) {
@ -188,7 +201,7 @@ class PDFDocument extends stream.Readable {
}
this.push(data);
return this._offset += data.length;
return (this._offset += data.length);
}
addContent(data) {
@ -198,9 +211,9 @@ class PDFDocument extends stream.Readable {
_refEnd(ref) {
this._offsets[ref.id - 1] = ref.offset;
if ((--this._waiting === 0) && this._ended) {
if (--this._waiting === 0 && this._ended) {
this._finalize();
return this._ended = false;
return (this._ended = false);
}
}
@ -209,8 +222,7 @@ class PDFDocument extends stream.Readable {
const err = new Error(`\
PDFDocument#write is deprecated, and will be removed in a future version of PDFKit. \
Please pipe the document into a Node stream.\
`
);
`);
console.warn(err.stack);
@ -224,8 +236,7 @@ Please pipe the document into a Node stream.\
throw new Error(`\
PDFDocument#output is deprecated, and has been removed from PDFKit. \
Please pipe the document into a Node stream.\
`
);
`);
}
end() {
@ -262,19 +273,19 @@ Please pipe the document into a Node stream.\
if (this._waiting === 0) {
return this._finalize();
} else {
return this._ended = true;
return (this._ended = true);
}
}
_finalize(fn) {
// generate xref
const xRefOffset = this._offset;
this._write("xref");
this._write('xref');
this._write(`0 ${this._offsets.length + 1}`);
this._write("0000000000 65535 f ");
this._write('0000000000 65535 f ');
for (let offset of this._offsets) {
offset = (`0000000000${offset}`).slice(-10);
offset = `0000000000${offset}`.slice(-10);
this._write(offset + ' 00000 n ');
}
@ -301,9 +312,9 @@ Please pipe the document into a Node stream.\
}
toString() {
return "[object PDFDocument]";
return '[object PDFDocument]';
}
};
}
const mixin = methods => {
Object.assign(PDFDocument.prototype, methods);

View File

@ -1,35 +1,40 @@
class PDFFont {
constructor() {
}
class PDFFont {
constructor() {}
encode() {
throw new Error('Must be implemented by subclasses');
}
widthOfString() {
throw new Error('Must be implemented by subclasses');
}
ref() {
return this.dictionary != null ? this.dictionary : (this.dictionary = this.document.ref());
return this.dictionary != null
? this.dictionary
: (this.dictionary = this.document.ref());
}
finalize() {
if (this.embedded || (this.dictionary == null)) { return; }
if (this.embedded || this.dictionary == null) {
return;
}
this.embed();
return this.embedded = true;
return (this.embedded = true);
}
embed() {
throw new Error('Must be implemented by subclasses');
}
lineHeight(size, includeGap) {
if (includeGap == null) { includeGap = false; }
if (includeGap == null) {
includeGap = false;
}
const gap = includeGap ? this.lineGap : 0;
return (((this.ascender + gap) - this.descender) / 1000) * size;
return ((this.ascender + gap - this.descender) / 1000) * size;
}
}
export default PDFFont;

View File

@ -1,7 +1,7 @@
import fs from 'fs';
const WIN_ANSI_MAP = {
402: 131,
402: 131,
8211: 150,
8212: 151,
8216: 145,
@ -18,16 +18,16 @@ const WIN_ANSI_MAP = {
8240: 137,
8249: 139,
8250: 155,
710: 136,
710: 136,
8482: 153,
338: 140,
339: 156,
732: 152,
352: 138,
353: 154,
376: 159,
381: 142,
382: 158
338: 140,
339: 156,
732: 152,
352: 138,
353: 154,
376: 159,
381: 142,
382: 158
};
const characters = `\
@ -104,8 +104,7 @@ oslash ugrave uacute ucircumflex
udieresis yacute thorn ydieresis\
`.split(/\s+/);
class AFMFont {
class AFMFont {
static open(filename) {
return new AFMFont(fs.readFileSync(filename, 'utf8'));
}
@ -116,20 +115,21 @@ class AFMFont {
this.glyphWidths = {};
this.boundingBoxes = {};
this.kernPairs = {};
this.parse();
// todo: remove charWidths since appears to not be used
this.charWidths = new Array(256);
this.charWidths = new Array(256);
for (let char = 0; char <= 255; char++) {
this.charWidths[char] = this.glyphWidths[characters[char]];
}
this.bbox = (this.attributes['FontBBox'].split(/\s+/).map((e) => +e));
}
this.bbox = this.attributes['FontBBox'].split(/\s+/).map(e => +e);
this.ascender = +(this.attributes['Ascender'] || 0);
this.descender = +(this.attributes['Descender'] || 0);
this.xHeight = +(this.attributes['XHeight'] || 0);
this.capHeight = +(this.attributes['CapHeight'] || 0);
this.lineGap = (this.bbox[3] - this.bbox[1]) - (this.ascender - this.descender);
this.lineGap =
this.bbox[3] - this.bbox[1] - (this.ascender - this.descender);
}
parse() {
@ -137,35 +137,38 @@ class AFMFont {
for (let line of this.contents.split('\n')) {
var match;
var a;
if (match = line.match(/^Start(\w+)/)) {
if ((match = line.match(/^Start(\w+)/))) {
section = match[1];
continue;
} else if (match = line.match(/^End(\w+)/)) {
} else if ((match = line.match(/^End(\w+)/))) {
section = '';
continue;
}
switch (section) {
case 'FontMetrics':
match = line.match(/(^\w+)\s+(.*)/);
var key = match[1];
var value = match[2];
if (a = this.attributes[key]) {
if (!Array.isArray(a)) { a = (this.attributes[key] = [a]); }
if ((a = this.attributes[key])) {
if (!Array.isArray(a)) {
a = this.attributes[key] = [a];
}
a.push(value);
} else {
this.attributes[key] = value;
}
break;
case 'CharMetrics':
if (!/^CH?\s/.test(line)) { continue; }
if (!/^CH?\s/.test(line)) {
continue;
}
var name = line.match(/\bN\s+(\.?\w+)\s*;/)[1];
this.glyphWidths[name] = +line.match(/\bWX\s+(\d+)\s*;/)[1];
break;
case 'KernPairs':
match = line.match(/^KPX\s+(\.?\w+)\s+(\.?\w+)\s+(-?\d+)/);
if (match) {
@ -174,54 +177,61 @@ class AFMFont {
break;
}
}
}
encodeText(text) {
const res = [];
for (let i = 0, end = text.length, asc = 0 <= end; asc ? i < end : i > end; asc ? i++ : i--) {
for (
let i = 0, end = text.length, asc = 0 <= end;
asc ? i < end : i > end;
asc ? i++ : i--
) {
let char = text.charCodeAt(i);
char = WIN_ANSI_MAP[char] || char;
res.push(char.toString(16));
}
return res;
}
glyphsForString(string) {
const glyphs = [];
for (let i = 0, end = string.length, asc = 0 <= end; asc ? i < end : i > end; asc ? i++ : i--) {
const charCode = string.charCodeAt(i);
for (
let i = 0, end = string.length, asc = 0 <= end;
asc ? i < end : i > end;
asc ? i++ : i--
) {
const charCode = string.charCodeAt(i);
glyphs.push(this.characterToGlyph(charCode));
}
return glyphs;
}
characterToGlyph(character) {
return characters[WIN_ANSI_MAP[character] || character] || '.notdef';
}
widthOfGlyph(glyph) {
return this.glyphWidths[glyph] || 0;
}
getKernPair(left, right) {
return this.kernPairs[left + '\0' + right] || 0;
}
advancesForGlyphs(glyphs) {
const advances = [];
for (let index = 0; index < glyphs.length; index++) {
const left = glyphs[index];
const right = glyphs[index + 1];
advances.push(this.widthOfGlyph(left) + this.getKernPair(left, right));
}
return advances;
}
};
export default AFMFont;
}
export default AFMFont;

View File

@ -1,210 +1,240 @@
import PDFFont from '../font';
const toHex = function(num) {
return (`0000${num.toString(16)}`).slice(-4);
return `0000${num.toString(16)}`.slice(-4);
};
class EmbeddedFont extends PDFFont {
constructor(document, font, id) {
super();
this.document = document;
this.font = font;
this.id = id;
this.subset = this.font.createSubset();
this.unicode = [[0]];
this.widths = [this.font.getGlyph(0).advanceWidth];
class EmbeddedFont extends PDFFont {
constructor(document, font, id) {
super();
this.document = document;
this.font = font;
this.id = id;
this.subset = this.font.createSubset();
this.unicode = [[0]];
this.widths = [this.font.getGlyph(0).advanceWidth];
this.name = this.font.postscriptName;
this.scale = 1000 / this.font.unitsPerEm;
this.ascender = this.font.ascent * this.scale;
this.descender = this.font.descent * this.scale;
this.xHeight = this.font.xHeight * this.scale;
this.capHeight = this.font.capHeight * this.scale;
this.lineGap = this.font.lineGap * this.scale;
this.bbox = this.font.bbox;
this.name = this.font.postscriptName;
this.scale = 1000 / this.font.unitsPerEm;
this.ascender = this.font.ascent * this.scale;
this.descender = this.font.descent * this.scale;
this.xHeight = this.font.xHeight * this.scale;
this.capHeight = this.font.capHeight * this.scale;
this.lineGap = this.font.lineGap * this.scale;
this.bbox = this.font.bbox;
this.layoutCache = Object.create(null);
this.layoutCache = Object.create(null);
}
layoutRun(text, features) {
const run = this.font.layout(text, features);
// Normalize position values
for (let i = 0; i < run.positions.length; i++) {
const position = run.positions[i];
for (let key in position) {
position[key] *= this.scale;
}
position.advanceWidth = run.glyphs[i].advanceWidth * this.scale;
}
layoutRun(text, features) {
const run = this.font.layout(text, features);
return run;
}
// Normalize position values
for (let i = 0; i < run.positions.length; i++) {
const position = run.positions[i];
for (let key in position) {
position[key] *= this.scale;
layoutCached(text) {
let cached;
if ((cached = this.layoutCache[text])) {
return cached;
}
const run = this.layoutRun(text);
this.layoutCache[text] = run;
return run;
}
layout(text, features, onlyWidth) {
// Skip the cache if any user defined features are applied
if (onlyWidth == null) {
onlyWidth = false;
}
if (features) {
return this.layoutRun(text, features);
}
const glyphs = onlyWidth ? null : [];
const positions = onlyWidth ? null : [];
let advanceWidth = 0;
// Split the string by words to increase cache efficiency.
// For this purpose, spaces and tabs are a good enough delimeter.
let last = 0;
let index = 0;
while (index <= text.length) {
var needle;
if (
(index === text.length && last < index) ||
((needle = text.charAt(index)), [' ', '\t'].includes(needle))
) {
const run = this.layoutCached(text.slice(last, ++index));
if (!onlyWidth) {
glyphs.push(...(run.glyphs || []));
positions.push(...(run.positions || []));
}
position.advanceWidth = run.glyphs[i].advanceWidth * this.scale;
}
return run;
}
layoutCached(text) {
let cached;
if (cached = this.layoutCache[text]) {
return cached;
}
const run = this.layoutRun(text);
this.layoutCache[text] = run;
return run;
}
layout(text, features, onlyWidth) {
// Skip the cache if any user defined features are applied
if (onlyWidth == null) { onlyWidth = false; }
if (features) {
return this.layoutRun(text, features);
}
const glyphs = onlyWidth ? null : [];
const positions = onlyWidth ? null : [];
let advanceWidth = 0;
// Split the string by words to increase cache efficiency.
// For this purpose, spaces and tabs are a good enough delimeter.
let last = 0;
let index = 0;
while (index <= text.length) {
var needle;
if (((index === text.length) && (last < index)) || (needle = text.charAt(index), [' ', '\t'].includes(needle))) {
const run = this.layoutCached(text.slice(last, ++index));
if (!onlyWidth) {
glyphs.push(...(run.glyphs || []));
positions.push(...(run.positions || []));
}
advanceWidth += run.advanceWidth;
last = index;
} else {
index++;
}
}
return {glyphs, positions, advanceWidth};
}
encode(text, features) {
const {glyphs, positions} = this.layout(text, features);
const res = [];
for (let i = 0; i < glyphs.length; i++) {
const glyph = glyphs[i];
const gid = this.subset.includeGlyph(glyph.id);
res.push((`0000${gid.toString(16)}`).slice(-4));
if (this.widths[gid] == null) { this.widths[gid] = glyph.advanceWidth * this.scale; }
if (this.unicode[gid] == null) { this.unicode[gid] = glyph.codePoints; }
}
return [res, positions];
}
widthOfString(string, size, features) {
const width = this.layout(string, features, true).advanceWidth;
const scale = size / 1000;
return width * scale;
}
embed() {
const isCFF = (this.subset.cff != null);
const fontFile = this.document.ref();
if (isCFF) {
fontFile.data.Subtype = 'CIDFontType0C';
}
this.subset.encodeStream()
.on('data', data => fontFile.write(data)).on('end', () => fontFile.end());
const familyClass = ((this.font['OS/2'] != null ? this.font['OS/2'].sFamilyClass : undefined) || 0) >> 8;
let flags = 0;
if (this.font.post.isFixedPitch) { flags |= 1 << 0; }
if (1 <= familyClass && familyClass <= 7) { flags |= 1 << 1; }
flags |= 1 << 2; // assume the font uses non-latin characters
if (familyClass === 10) { flags |= 1 << 3; }
if (this.font.head.macStyle.italic) { flags |= 1 << 6; }
// generate a tag (6 uppercase letters. 16 is the char code offset from '1' to 'A'. 74 will map to 'Z')
const tag = ([1, 2, 3, 4, 5, 6].map((i) => String.fromCharCode((this.id.charCodeAt(i) || 74) + 16))).join('');
const name = tag + '+' + this.font.postscriptName;
const { bbox } = this.font;
const descriptor = this.document.ref({
Type: 'FontDescriptor',
FontName: name,
Flags: flags,
FontBBox: [bbox.minX * this.scale, bbox.minY * this.scale, bbox.maxX * this.scale, bbox.maxY * this.scale],
ItalicAngle: this.font.italicAngle,
Ascent: this.ascender,
Descent: this.descender,
CapHeight: (this.font.capHeight || this.font.ascent) * this.scale,
XHeight: (this.font.xHeight || 0) * this.scale,
StemV: 0
}); // not sure how to calculate this
if (isCFF) {
descriptor.data.FontFile3 = fontFile;
advanceWidth += run.advanceWidth;
last = index;
} else {
descriptor.data.FontFile2 = fontFile;
index++;
}
descriptor.end();
const descendantFont = this.document.ref({
Type: 'Font',
Subtype: isCFF ? 'CIDFontType0' : 'CIDFontType2',
BaseFont: name,
CIDSystemInfo: {
Registry: new String('Adobe'),
Ordering: new String('Identity'),
Supplement: 0
},
FontDescriptor: descriptor,
W: [0, this.widths]});
descendantFont.end();
this.dictionary.data = {
Type: 'Font',
Subtype: 'Type0',
BaseFont: name,
Encoding: 'Identity-H',
DescendantFonts: [descendantFont],
ToUnicode: this.toUnicodeCmap()
};
return this.dictionary.end();
}
// Maps the glyph ids encoded in the PDF back to unicode strings
// Because of ligature substitutions and the like, there may be one or more
// unicode characters represented by each glyph.
toUnicodeCmap() {
const cmap = this.document.ref();
return { glyphs, positions, advanceWidth };
}
const entries = [];
for (let codePoints of this.unicode) {
const encoded = [];
encode(text, features) {
const { glyphs, positions } = this.layout(text, features);
// encode codePoints to utf16
for (let value of codePoints) {
if (value > 0xffff) {
value -= 0x10000;
encoded.push(toHex(((value >>> 10) & 0x3ff) | 0xd800));
value = 0xdc00 | (value & 0x3ff);
}
const res = [];
for (let i = 0; i < glyphs.length; i++) {
const glyph = glyphs[i];
const gid = this.subset.includeGlyph(glyph.id);
res.push(`0000${gid.toString(16)}`.slice(-4));
encoded.push(toHex(value));
if (this.widths[gid] == null) {
this.widths[gid] = glyph.advanceWidth * this.scale;
}
if (this.unicode[gid] == null) {
this.unicode[gid] = glyph.codePoints;
}
}
return [res, positions];
}
widthOfString(string, size, features) {
const width = this.layout(string, features, true).advanceWidth;
const scale = size / 1000;
return width * scale;
}
embed() {
const isCFF = this.subset.cff != null;
const fontFile = this.document.ref();
if (isCFF) {
fontFile.data.Subtype = 'CIDFontType0C';
}
this.subset
.encodeStream()
.on('data', data => fontFile.write(data))
.on('end', () => fontFile.end());
const familyClass =
((this.font['OS/2'] != null
? this.font['OS/2'].sFamilyClass
: undefined) || 0) >> 8;
let flags = 0;
if (this.font.post.isFixedPitch) {
flags |= 1 << 0;
}
if (1 <= familyClass && familyClass <= 7) {
flags |= 1 << 1;
}
flags |= 1 << 2; // assume the font uses non-latin characters
if (familyClass === 10) {
flags |= 1 << 3;
}
if (this.font.head.macStyle.italic) {
flags |= 1 << 6;
}
// generate a tag (6 uppercase letters. 16 is the char code offset from '1' to 'A'. 74 will map to 'Z')
const tag = [1, 2, 3, 4, 5, 6]
.map(i => String.fromCharCode((this.id.charCodeAt(i) || 74) + 16))
.join('');
const name = tag + '+' + this.font.postscriptName;
const { bbox } = this.font;
const descriptor = this.document.ref({
Type: 'FontDescriptor',
FontName: name,
Flags: flags,
FontBBox: [
bbox.minX * this.scale,
bbox.minY * this.scale,
bbox.maxX * this.scale,
bbox.maxY * this.scale
],
ItalicAngle: this.font.italicAngle,
Ascent: this.ascender,
Descent: this.descender,
CapHeight: (this.font.capHeight || this.font.ascent) * this.scale,
XHeight: (this.font.xHeight || 0) * this.scale,
StemV: 0
}); // not sure how to calculate this
if (isCFF) {
descriptor.data.FontFile3 = fontFile;
} else {
descriptor.data.FontFile2 = fontFile;
}
descriptor.end();
const descendantFont = this.document.ref({
Type: 'Font',
Subtype: isCFF ? 'CIDFontType0' : 'CIDFontType2',
BaseFont: name,
CIDSystemInfo: {
Registry: new String('Adobe'),
Ordering: new String('Identity'),
Supplement: 0
},
FontDescriptor: descriptor,
W: [0, this.widths]
});
descendantFont.end();
this.dictionary.data = {
Type: 'Font',
Subtype: 'Type0',
BaseFont: name,
Encoding: 'Identity-H',
DescendantFonts: [descendantFont],
ToUnicode: this.toUnicodeCmap()
};
return this.dictionary.end();
}
// Maps the glyph ids encoded in the PDF back to unicode strings
// Because of ligature substitutions and the like, there may be one or more
// unicode characters represented by each glyph.
toUnicodeCmap() {
const cmap = this.document.ref();
const entries = [];
for (let codePoints of this.unicode) {
const encoded = [];
// encode codePoints to utf16
for (let value of codePoints) {
if (value > 0xffff) {
value -= 0x10000;
encoded.push(toHex(((value >>> 10) & 0x3ff) | 0xd800));
value = 0xdc00 | (value & 0x3ff);
}
entries.push(`<${encoded.join(' ')}>`);
encoded.push(toHex(value));
}
cmap.end(`\
entries.push(`<${encoded.join(' ')}>`);
}
cmap.end(`\
/CIDInit /ProcSet findresource begin
12 dict begin
begincmap
@ -225,12 +255,10 @@ endcmap
CMapName currentdict /CMap defineresource pop
end
end\
`
);
`);
return cmap;
}
};
return cmap;
}
}
export default EmbeddedFont;

View File

@ -4,20 +4,51 @@ import fs from 'fs';
// This insanity is so bundlers can inline the font files
const STANDARD_FONTS = {
"Courier"() { return fs.readFileSync(__dirname + "/data/Courier.afm", 'utf8'); },
"Courier-Bold"() { return fs.readFileSync(__dirname + "/data/Courier-Bold.afm", 'utf8'); },
"Courier-Oblique"() { return fs.readFileSync(__dirname + "/data/Courier-Oblique.afm", 'utf8'); },
"Courier-BoldOblique"() { return fs.readFileSync(__dirname + "/data/Courier-BoldOblique.afm", 'utf8'); },
"Helvetica"() { return fs.readFileSync(__dirname + "/data/Helvetica.afm", 'utf8'); },
"Helvetica-Bold"() { return fs.readFileSync(__dirname + "/data/Helvetica-Bold.afm", 'utf8'); },
"Helvetica-Oblique"() { return fs.readFileSync(__dirname + "/data/Helvetica-Oblique.afm", 'utf8'); },
"Helvetica-BoldOblique"() { return fs.readFileSync(__dirname + "/data/Helvetica-BoldOblique.afm", 'utf8'); },
"Times-Roman"() { return fs.readFileSync(__dirname + "/data/Times-Roman.afm", 'utf8'); },
"Times-Bold"() { return fs.readFileSync(__dirname + "/data/Times-Bold.afm", 'utf8'); },
"Times-Italic"() { return fs.readFileSync(__dirname + "/data/Times-Italic.afm", 'utf8'); },
"Times-BoldItalic"() { return fs.readFileSync(__dirname + "/data/Times-BoldItalic.afm", 'utf8'); },
"Symbol"() { return fs.readFileSync(__dirname + "/data/Symbol.afm", 'utf8'); },
"ZapfDingbats"() { return fs.readFileSync(__dirname + "/data/ZapfDingbats.afm", 'utf8'); }
Courier() {
return fs.readFileSync(__dirname + '/data/Courier.afm', 'utf8');
},
'Courier-Bold'() {
return fs.readFileSync(__dirname + '/data/Courier-Bold.afm', 'utf8');
},
'Courier-Oblique'() {
return fs.readFileSync(__dirname + '/data/Courier-Oblique.afm', 'utf8');
},
'Courier-BoldOblique'() {
return fs.readFileSync(__dirname + '/data/Courier-BoldOblique.afm', 'utf8');
},
Helvetica() {
return fs.readFileSync(__dirname + '/data/Helvetica.afm', 'utf8');
},
'Helvetica-Bold'() {
return fs.readFileSync(__dirname + '/data/Helvetica-Bold.afm', 'utf8');
},
'Helvetica-Oblique'() {
return fs.readFileSync(__dirname + '/data/Helvetica-Oblique.afm', 'utf8');
},
'Helvetica-BoldOblique'() {
return fs.readFileSync(
__dirname + '/data/Helvetica-BoldOblique.afm',
'utf8'
);
},
'Times-Roman'() {
return fs.readFileSync(__dirname + '/data/Times-Roman.afm', 'utf8');
},
'Times-Bold'() {
return fs.readFileSync(__dirname + '/data/Times-Bold.afm', 'utf8');
},
'Times-Italic'() {
return fs.readFileSync(__dirname + '/data/Times-Italic.afm', 'utf8');
},
'Times-BoldItalic'() {
return fs.readFileSync(__dirname + '/data/Times-BoldItalic.afm', 'utf8');
},
Symbol() {
return fs.readFileSync(__dirname + '/data/Symbol.afm', 'utf8');
},
ZapfDingbats() {
return fs.readFileSync(__dirname + '/data/ZapfDingbats.afm', 'utf8');
}
};
class StandardFont extends PDFFont {
@ -27,9 +58,16 @@ class StandardFont extends PDFFont {
this.name = name;
this.id = id;
this.font = new AFMFont(STANDARD_FONTS[this.name]());
({ascender: this.ascender,descender: this.descender,bbox: this.bbox,lineGap: this.lineGap,xHeight: this.xHeight,capHeight: this.capHeight} = this.font);
({
ascender: this.ascender,
descender: this.descender,
bbox: this.bbox,
lineGap: this.lineGap,
xHeight: this.xHeight,
capHeight: this.capHeight
} = this.font);
}
embed() {
this.dictionary.data = {
Type: 'Font',
@ -37,10 +75,10 @@ class StandardFont extends PDFFont {
Subtype: 'Type1',
Encoding: 'WinAnsiEncoding'
};
return this.dictionary.end();
}
encode(text) {
const encoded = this.font.encodeText(text);
const glyphs = this.font.glyphsForString(`${text}`);
@ -56,27 +94,26 @@ class StandardFont extends PDFFont {
advanceWidth: this.font.widthOfGlyph(glyph)
});
}
return [encoded, positions];
}
widthOfString(string, size) {
const glyphs = this.font.glyphsForString(`${string}`);
const advances = this.font.advancesForGlyphs(glyphs);
let width = 0;
for (let advance of advances) {
width += advance;
}
const scale = size / 1000;
const scale = size / 1000;
return width * scale;
}
static isStandardFont(name) {
return name in STANDARD_FONTS;
}
};
}
export default StandardFont;

View File

@ -9,26 +9,22 @@ class PDFFontFactory {
if (StandardFont.isStandardFont(src)) {
return new StandardFont(document, src, id);
}
font = fontkit.openSync(src, family);
} else if (Buffer.isBuffer(src)) {
font = fontkit.create(src, family);
} else if (src instanceof Uint8Array) {
font = fontkit.create(new Buffer(src), family);
} else if (src instanceof ArrayBuffer) {
font = fontkit.create(new Buffer(new Uint8Array(src)), family);
}
if ((font == null)) {
if (font == null) {
throw new Error('Not a supported font format or standard PDF font.');
}
return new EmbeddedFont(document, font, id);
}
}
export default PDFFontFactory;

View File

@ -1,8 +1,6 @@
import PDFObject from './object';
const {
number
} = PDFObject;
const { number } = PDFObject;
class PDFGradient {
constructor(doc) {
@ -11,9 +9,11 @@ class PDFGradient {
this.embedded = false;
this.transform = [1, 0, 0, 1, 0, 0];
}
stop(pos, color, opacity) {
if (opacity == null) { opacity = 1; }
if (opacity == null) {
opacity = 1;
}
color = this.doc._normalizeColor(color);
if (this.stops.length === 0) {
@ -26,9 +26,11 @@ class PDFGradient {
} else {
throw new Error('Unknown color space');
}
} else if (((this._colorSpace === 'DeviceRGB') && (color.length !== 3)) ||
((this._colorSpace === 'DeviceCMYK') && (color.length !== 4)) ||
((this._colorSpace === 'DeviceGray') && (color.length !== 1))) {
} else if (
(this._colorSpace === 'DeviceRGB' && color.length !== 3) ||
(this._colorSpace === 'DeviceCMYK' && color.length !== 4) ||
(this._colorSpace === 'DeviceGray' && color.length !== 1)
) {
throw new Error('All gradient stops must use the same color space');
}
@ -45,26 +47,32 @@ class PDFGradient {
embed(m) {
let asc, i;
let end, fn;
if (this.stops.length === 0) { return; }
if (this.stops.length === 0) {
return;
}
this.embedded = true;
this.matrix = m;
// if the last stop comes before 100%, add a copy at 100%
const last = this.stops[this.stops.length - 1];
if (last[0] < 1) {
this.stops.push([1, last[1], last[2]]);
}
const bounds = [];
const encode = [];
const stops = [];
for (i = 0, end = this.stops.length - 1, asc = 0 <= end; asc ? i < end : i > end; asc ? i++ : i--) {
for (
i = 0, end = this.stops.length - 1, asc = 0 <= end;
asc ? i < end : i > end;
asc ? i++ : i--
) {
encode.push(0, 1);
if ((i + 2) !== this.stops.length) {
if (i + 2 !== this.stops.length) {
bounds.push(this.stops[i + 1][0]);
}
fn = this.doc.ref({
FunctionType: 2,
Domain: [0, 1],
@ -72,11 +80,11 @@ class PDFGradient {
C1: this.stops[i + 1][1],
N: 1
});
stops.push(fn);
fn.end();
}
// if there are only two stops, we don't need a stitching function
if (stops.length === 1) {
fn = stops[0];
@ -88,36 +96,36 @@ class PDFGradient {
Bounds: bounds,
Encode: encode
});
fn.end();
}
this.id = `Sh${++this.doc._gradCount}`;
const shader = this.shader(fn);
shader.end();
const pattern = this.doc.ref({
Type: 'Pattern',
PatternType: 2,
Shading: shader,
Matrix: (this.matrix.map((v) => number(v)))
Matrix: this.matrix.map(v => number(v))
});
pattern.end();
if (this.stops.some(stop => stop[2] < 1)) {
let grad = this.opacityGradient();
grad._colorSpace = 'DeviceGray';
for (let stop of this.stops) {
grad.stop(stop[0], [stop[2]]);
}
grad = grad.embed(this.matrix);
const pageBBox = [0, 0, this.doc.page.width, this.doc.page.height];
const form = this.doc.ref({
Type: 'XObject',
Subtype: 'Form',
@ -135,10 +143,10 @@ class PDFGradient {
}
}
});
form.write("/Pattern cs /Sh1 scn");
form.end(`${pageBBox.join(" ")} re f`);
form.write('/Pattern cs /Sh1 scn');
form.end(`${pageBBox.join(' ')} re f`);
const gstate = this.doc.ref({
Type: 'ExtGState',
SMask: {
@ -147,9 +155,9 @@ class PDFGradient {
G: form
}
});
gstate.end();
const opacityPattern = this.doc.ref({
Type: 'Pattern',
PatternType: 1,
@ -168,16 +176,15 @@ class PDFGradient {
}
}
});
opacityPattern.write("/Gs1 gs /Pattern cs /Sh1 scn");
opacityPattern.end(`${pageBBox.join(" ")} re f`);
opacityPattern.write('/Gs1 gs /Pattern cs /Sh1 scn');
opacityPattern.end(`${pageBBox.join(' ')} re f`);
this.doc.page.patterns[this.id] = opacityPattern;
} else {
this.doc.page.patterns[this.id] = pattern;
}
return pattern;
}
@ -185,14 +192,18 @@ class PDFGradient {
// apply gradient transform to existing document ctm
const [m0, m1, m2, m3, m4, m5] = this.doc._ctm;
const [m11, m12, m21, m22, dx, dy] = this.transform;
const m = [(m0 * m11) + (m2 * m12),
(m1 * m11) + (m3 * m12),
(m0 * m21) + (m2 * m22),
(m1 * m21) + (m3 * m22),
(m0 * dx) + (m2 * dy) + m4,
(m1 * dx) + (m3 * dy) + m5];
const m = [
m0 * m11 + m2 * m12,
m1 * m11 + m3 * m12,
m0 * m21 + m2 * m22,
m1 * m21 + m3 * m22,
m0 * dx + m2 * dy + m4,
m1 * dx + m3 * dy + m5
];
if (!this.embedded || (m.join(" ") !== this.matrix.join(" "))) { this.embed(m); }
if (!this.embedded || m.join(' ') !== this.matrix.join(' ')) {
this.embed(m);
}
return this.doc.addContent(`/${this.id} ${op}`);
}
}
@ -203,18 +214,19 @@ class PDFLinearGradient extends PDFGradient {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
this.y2 = y2;
}
shader(fn) {
return this.doc.ref({
ShadingType: 2,
ColorSpace: this._colorSpace,
Coords: [this.x1, this.y1, this.x2, this.y2],
Function: fn,
Extend: [true, true]});
Extend: [true, true]
});
}
opacityGradient() {
return new PDFLinearGradient(this.doc, this.x1, this.y1, this.x2, this.y2);
}
@ -229,21 +241,30 @@ class PDFRadialGradient extends PDFGradient {
this.r1 = r1;
this.x2 = x2;
this.y2 = y2;
this.r2 = r2;
this.r2 = r2;
}
shader(fn) {
return this.doc.ref({
ShadingType: 3,
ColorSpace: this._colorSpace,
Coords: [this.x1, this.y1, this.r1, this.x2, this.y2, this.r2],
Function: fn,
Extend: [true, true]});
Extend: [true, true]
});
}
opacityGradient() {
return new PDFRadialGradient(this.doc, this.x1, this.y1, this.r1, this.x2, this.y2, this.r2);
return new PDFRadialGradient(
this.doc,
this.x1,
this.y1,
this.r1,
this.x2,
this.y2,
this.r2
);
}
}
export default {PDFGradient, PDFLinearGradient, PDFRadialGradient};
export default { PDFGradient, PDFLinearGradient, PDFRadialGradient };

View File

@ -16,25 +16,24 @@ class PDFImage {
data = new Buffer(new Uint8Array(src));
} else {
let match;
if (match = /^data:.+;base64,(.*)$/.exec(src)) {
if ((match = /^data:.+;base64,(.*)$/.exec(src))) {
data = new Buffer(match[1], 'base64');
} else {
data = fs.readFileSync(src);
if (!data) { return; }
if (!data) {
return;
}
}
}
if ((data[0] === 0xff) && (data[1] === 0xd8)) {
if (data[0] === 0xff && data[1] === 0xd8) {
return new JPEG(data, label);
} else if ((data[0] === 0x89) && (data.toString('ascii', 1, 4) === 'PNG')) {
} else if (data[0] === 0x89 && data.toString('ascii', 1, 4) === 'PNG') {
return new PNG(data, label);
} else {
throw new Error('Unknown image format.');
}
}
}
export default PDFImage;
export default PDFImage;

View File

@ -1,30 +1,49 @@
const MARKERS = [0xFFC0, 0xFFC1, 0xFFC2, 0xFFC3, 0xFFC5, 0xFFC6, 0xFFC7,
0xFFC8, 0xFFC9, 0xFFCA, 0xFFCB, 0xFFCC, 0xFFCD, 0xFFCE, 0xFFCF];
const MARKERS = [
0xffc0,
0xffc1,
0xffc2,
0xffc3,
0xffc5,
0xffc6,
0xffc7,
0xffc8,
0xffc9,
0xffca,
0xffcb,
0xffcc,
0xffcd,
0xffce,
0xffcf
];
const COLOR_SPACE_MAP = {
1: 'DeviceGray',
3: 'DeviceRGB',
4: 'DeviceCMYK'
}
};
class JPEG {
constructor(data, label) {
let marker;
this.data = data;
this.label = label;
if (this.data.readUInt16BE(0) !== 0xFFD8) {
throw "SOI not found in JPEG";
if (this.data.readUInt16BE(0) !== 0xffd8) {
throw 'SOI not found in JPEG';
}
let pos = 2;
while (pos < this.data.length) {
marker = this.data.readUInt16BE(pos);
pos += 2;
if (MARKERS.includes(marker)) { break; }
if (MARKERS.includes(marker)) {
break;
}
pos += this.data.readUInt16BE(pos);
}
if (!MARKERS.includes(marker)) { throw "Invalid JPEG."; }
if (!MARKERS.includes(marker)) {
throw 'Invalid JPEG.';
}
pos += 2;
this.bits = this.data[pos++];
@ -36,13 +55,15 @@ class JPEG {
const channels = this.data[pos++];
this.colorSpace = COLOR_SPACE_MAP[channels];
this.obj = null;
}
embed(document) {
if (this.obj) { return; }
if (this.obj) {
return;
}
this.obj = document.ref({
Type: 'XObject',
Subtype: 'Image',
@ -52,19 +73,19 @@ class JPEG {
ColorSpace: this.colorSpace,
Filter: 'DCTDecode'
});
// add extra decode params for CMYK images. By swapping the
// min and max values from the default, we invert the colors. See
// section 4.8.4 of the spec.
// section 4.8.4 of the spec.
if (this.colorSpace === 'DeviceCMYK') {
this.obj.data['Decode'] = [1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0];
}
this.obj.end(this.data);
// free memory
return this.data = null;
return (this.data = null);
}
};
}
export default JPEG;

View File

@ -13,7 +13,9 @@ class PNGImage {
embed(document) {
this.document = document;
if (this.obj) { return; }
if (this.obj) {
return;
}
const hasAlphaChannel = this.image.hasAlphaChannel;
@ -46,7 +48,12 @@ class PNGImage {
palette.end(new Buffer(this.image.palette));
// build the color space array for the image
this.obj.data['ColorSpace'] = ['Indexed', 'DeviceRGB', (this.image.palette.length / 3) - 1, palette];
this.obj.data['ColorSpace'] = [
'Indexed',
'DeviceRGB',
this.image.palette.length / 3 - 1,
palette
];
}
// For PNG color types 0, 2 and 3, the transparency data is stored in
@ -56,7 +63,6 @@ class PNGImage {
// An array with N elements, where N is two times the number of color components.
const val = this.image.transparency.grayscale;
this.obj.data['Mask'] = [val, val];
} else if (this.image.transparency.rgb) {
// Use Color Key Masking (spec section 4.8.5)
// An array with N elements, where N is two times the number of color components.
@ -67,18 +73,15 @@ class PNGImage {
}
this.obj.data['Mask'] = mask;
} else if (this.image.transparency.indexed) {
// Create a transparency SMask for the image based on the data
// in the PLTE and tRNS sections. See below for details on SMasks.
return this.loadIndexedAlphaChannel();
} else if (hasAlphaChannel) {
// For PNG color types 4 and 6, the transparency data is stored as a alpha
// channel mixed in with the main image data. Separate this data out into an
// SMask object and store it separately in the PDF.
return this.splitAlphaChannel();
}
this.finalize();
}
@ -93,7 +96,8 @@ class PNGImage {
BitsPerComponent: 8,
Filter: 'FlateDecode',
ColorSpace: 'DeviceGray',
Decode: [0, 1]});
Decode: [0, 1]
});
sMask.end(this.alphaChannel);
this.obj.data['SMask'] = sMask;
@ -104,7 +108,7 @@ class PNGImage {
// free memory
this.image = null;
return this.imgData = null;
return (this.imgData = null);
}
splitAlphaChannel() {
@ -113,9 +117,9 @@ class PNGImage {
const colorCount = this.image.colors;
const pixelCount = this.width * this.height;
const imgData = new Buffer(pixelCount * colorCount);
const alphaChannel = new Buffer(pixelCount);
const alphaChannel = new Buffer(pixelCount);
let i = p = a = 0;
let i = (p = a = 0);
const len = pixels.length;
// For 16bit images copy only most significant byte (MSB) - PNG data is always stored in network byte order (MSB first)
const skipByteCount = this.image.bits === 16 ? 1 : 0;

View File

@ -5,20 +5,21 @@ class LineWrapper extends EventEmitter {
constructor(document, options) {
super();
this.document = document;
this.indent = options.indent || 0;
this.indent = options.indent || 0;
this.characterSpacing = options.characterSpacing || 0;
this.wordSpacing = options.wordSpacing === 0;
this.columns = options.columns || 1;
this.columnGap = options.columnGap != null ? options.columnGap : 18; // 1/4 inch
this.lineWidth = (options.width - (this.columnGap * (this.columns - 1))) / this.columns;
this.columns = options.columns || 1;
this.columnGap = options.columnGap != null ? options.columnGap : 18; // 1/4 inch
this.lineWidth =
(options.width - this.columnGap * (this.columns - 1)) / this.columns;
this.spaceLeft = this.lineWidth;
this.startX = this.document.x;
this.startY = this.document.y;
this.column = 1;
this.ellipsis = options.ellipsis;
this.continuedX = 0;
this.startX = this.document.x;
this.startY = this.document.y;
this.column = 1;
this.ellipsis = options.ellipsis;
this.continuedX = 0;
this.features = options.features;
// calculate the maximum Y position the text can appear at
if (options.height != null) {
this.height = options.height;
@ -26,7 +27,7 @@ class LineWrapper extends EventEmitter {
} else {
this.maxY = this.document.page.maxY();
}
// handle paragraph indents
this.on('firstLine', options => {
// if this is the first line of the text segment, and
@ -35,50 +36,64 @@ class LineWrapper extends EventEmitter {
const indent = this.continuedX || this.indent;
this.document.x += indent;
this.lineWidth -= indent;
return this.once('line', () => {
this.document.x -= indent;
this.lineWidth += indent;
if (options.continued && !this.continuedX) {
this.continuedX = this.indent;
}
if (!options.continued) { return this.continuedX = 0; }
if (!options.continued) {
return (this.continuedX = 0);
}
});
});
// handle left aligning last lines of paragraphs
this.on('lastLine', options => {
const { align } = options;
if (align === 'justify') { options.align = 'left'; }
if (align === 'justify') {
options.align = 'left';
}
this.lastLine = true;
return this.once('line', () => {
this.document.y += options.paragraphGap || 0;
options.align = align;
return this.lastLine = false;
return (this.lastLine = false);
});
});
}
wordWidth(word) {
return this.document.widthOfString(word, this) + this.characterSpacing + this.wordSpacing;
return (
this.document.widthOfString(word, this) +
this.characterSpacing +
this.wordSpacing
);
}
eachWord(text, fn) {
// setup a unicode line breaker
let bk;
const breaker = new LineBreaker(text);
let last = null;
const wordWidths = Object.create(null);
while ((bk = breaker.nextBreak())) {
var shouldContinue;
let word = text.slice((last != null ? last.position : undefined) || 0, bk.position);
let w = wordWidths[word] != null ? wordWidths[word] : (wordWidths[word] = this.wordWidth(word));
let word = text.slice(
(last != null ? last.position : undefined) || 0,
bk.position
);
let w =
wordWidths[word] != null
? wordWidths[word]
: (wordWidths[word] = this.wordWidth(word));
// if the word is longer than the whole line, chop it up
// TODO: break by grapheme clusters, not JS string characters
if (w > (this.lineWidth + this.continuedX)) {
if (w > this.lineWidth + this.continuedX) {
// make some fake break objects
let lbk = last;
const fbk = {};
@ -91,101 +106,119 @@ class LineWrapper extends EventEmitter {
// an issue with long loops when processing massive words, such as a huge number of spaces
l = Math.ceil(this.spaceLeft / (w / word.length));
w = this.wordWidth(word.slice(0, l));
mightGrow = (w <= this.spaceLeft) && (l < word.length);
mightGrow = w <= this.spaceLeft && l < word.length;
} else {
l = word.length;
}
let mustShrink = (w > this.spaceLeft) && (l > 0);
let mustShrink = w > this.spaceLeft && l > 0;
// shrink or grow word as necessary after our near-guess above
while (mustShrink || mightGrow) {
if (mustShrink) {
w = this.wordWidth(word.slice(0, --l));
mustShrink = (w > this.spaceLeft) && (l > 0);
mustShrink = w > this.spaceLeft && l > 0;
} else {
w = this.wordWidth(word.slice(0, ++l));
mustShrink = (w > this.spaceLeft) && (l > 0);
mightGrow = (w <= this.spaceLeft) && (l < word.length);
mustShrink = w > this.spaceLeft && l > 0;
mightGrow = w <= this.spaceLeft && l < word.length;
}
}
// send a required break unless this is the last piece and a linebreak is not specified
fbk.required = bk.required || (l < word.length);
fbk.required = bk.required || l < word.length;
shouldContinue = fn(word.slice(0, l), w, fbk, lbk);
lbk = {required: false};
lbk = { required: false };
// get the remaining piece of the word
word = word.slice(l);
w = this.wordWidth(word);
if (shouldContinue === false) { break; }
if (shouldContinue === false) {
break;
}
}
} else {
// otherwise just emit the break as it was given to us
shouldContinue = fn(word, w, bk, last);
}
if (shouldContinue === false) { break; }
if (shouldContinue === false) {
break;
}
last = bk;
}
}
wrap(text, options) {
// override options from previous continued fragments
if (options.indent != null) { this.indent = options.indent; }
if (options.characterSpacing != null) { this.characterSpacing = options.characterSpacing; }
if (options.wordSpacing != null) { this.wordSpacing = options.wordSpacing; }
if (options.ellipsis != null) { this.ellipsis = options.ellipsis; }
// make sure we're actually on the page
// and that the first line of is never by
if (options.indent != null) {
this.indent = options.indent;
}
if (options.characterSpacing != null) {
this.characterSpacing = options.characterSpacing;
}
if (options.wordSpacing != null) {
this.wordSpacing = options.wordSpacing;
}
if (options.ellipsis != null) {
this.ellipsis = options.ellipsis;
}
// make sure we're actually on the page
// and that the first line of is never by
// itself at the bottom of a page (orphans)
const nextY = this.document.y + this.document.currentLineHeight(true);
if ((this.document.y > this.maxY) || (nextY > this.maxY)) {
if (this.document.y > this.maxY || nextY > this.maxY) {
this.nextSection();
}
let buffer = '';
let textWidth = 0;
let wc = 0;
let lc = 0;
let { y } = this.document; // used to reset Y pos if options.continued (below)
const emitLine = () => {
options.textWidth = textWidth + (this.wordSpacing * (wc - 1));
options.textWidth = textWidth + this.wordSpacing * (wc - 1);
options.wordCount = wc;
options.lineWidth = this.lineWidth;
({ y } = this.document);
this.emit('line', buffer, options, this);
return lc++;
};
this.emit('sectionStart', options, this);
this.eachWord(text, (word, w, bk, last) => {
if ((last == null) || last.required) {
if (last == null || last.required) {
this.emit('firstLine', options, this);
this.spaceLeft = this.lineWidth;
}
if (w <= this.spaceLeft) {
buffer += word;
textWidth += w;
wc++;
}
if (bk.required || (w > this.spaceLeft)) {
if (bk.required || w > this.spaceLeft) {
// if the user specified a max height and an ellipsis, and is about to pass the
// max height and max columns after the next line, append the ellipsis
const lh = this.document.currentLineHeight(true);
if ((this.height != null) && this.ellipsis && ((this.document.y + (lh * 2)) > this.maxY) && (this.column >= this.columns)) {
if (this.ellipsis === true) { this.ellipsis = '…'; } // map default ellipsis character
if (
this.height != null &&
this.ellipsis &&
this.document.y + lh * 2 > this.maxY &&
this.column >= this.columns
) {
if (this.ellipsis === true) {
this.ellipsis = '…';
} // map default ellipsis character
buffer = buffer.replace(/\s+$/, '');
textWidth = this.wordWidth(buffer + this.ellipsis);
// remove characters from the buffer until the ellipsis fits
// to avoid inifinite loop need to stop while-loop if buffer is empty string
while (buffer && (textWidth > this.lineWidth)) {
while (buffer && textWidth > this.lineWidth) {
buffer = buffer.slice(0, -1).replace(/\s+$/, '');
textWidth = this.wordWidth(buffer + this.ellipsis);
}
@ -209,12 +242,12 @@ class LineWrapper extends EventEmitter {
}
emitLine();
// if we've reached the edge of the page,
// if we've reached the edge of the page,
// continue on a new page or column
if ((this.document.y + lh) > this.maxY) {
if (this.document.y + lh > this.maxY) {
const shouldContinue = this.nextSection();
// stop if we reached the maximum height
if (!shouldContinue) {
wc = 0;
@ -222,68 +255,74 @@ class LineWrapper extends EventEmitter {
return false;
}
}
// reset the space left and buffer
if (bk.required) {
this.spaceLeft = this.lineWidth;
buffer = '';
textWidth = 0;
return wc = 0;
return (wc = 0);
} else {
// reset the space left and buffer
this.spaceLeft = this.lineWidth - w;
buffer = word;
textWidth = w;
return wc = 1;
return (wc = 1);
}
} else {
return this.spaceLeft -= w;
return (this.spaceLeft -= w);
}
});
if (wc > 0) {
this.emit('lastLine', options, this);
emitLine();
}
this.emit('sectionEnd', options, this);
// if the wrap is set to be continued, save the X position
// to start the first line of the next segment at, and reset
// the y position
if (options.continued === true) {
if (lc > 1) { this.continuedX = 0; }
if (lc > 1) {
this.continuedX = 0;
}
this.continuedX += options.textWidth || 0;
return this.document.y = y;
return (this.document.y = y);
} else {
return this.document.x = this.startX;
return (this.document.x = this.startX);
}
}
nextSection(options) {
this.emit('sectionEnd', options, this);
if (++this.column > this.columns) {
// if a max height was specified by the user, we're done.
// otherwise, the default is to make a new page at the bottom.
if (this.height != null) { return false; }
if (this.height != null) {
return false;
}
this.document.addPage();
this.column = 1;
this.startY = this.document.page.margins.top;
this.maxY = this.document.page.maxY();
this.document.x = this.startX;
if (this.document._fillColor) { this.document.fillColor(...(this.document._fillColor || [])); }
if (this.document._fillColor) {
this.document.fillColor(...(this.document._fillColor || []));
}
this.emit('pageBreak', options, this);
} else {
this.document.x += this.lineWidth + this.columnGap;
this.document.y = this.startY;
this.emit('columnBreak', options, this);
}
this.emit('sectionStart', options, this);
return true;
}
}
export default LineWrapper;

View File

@ -1,52 +1,62 @@
export default {
export default {
annotate(x, y, w, h, options) {
options.Type = 'Annot';
options.Rect = this._convertRect(x, y, w, h);
options.Border = [0, 0, 0];
if (options.Subtype !== 'Link') { if (options.C == null) { options.C = this._normalizeColor(options.color || [0, 0, 0]); } } // convert colors
if (options.Subtype !== 'Link') {
if (options.C == null) {
options.C = this._normalizeColor(options.color || [0, 0, 0]);
}
} // convert colors
delete options.color;
if (typeof options.Dest === 'string') {
options.Dest = new String(options.Dest);
}
// Capitalize keys
// Capitalize keys
for (let key in options) {
const val = options[key];
options[key[0].toUpperCase() + key.slice(1)] = val;
}
const ref = this.ref(options);
this.page.annotations.push(ref);
ref.end();
return this;
},
note(x, y, w, h, contents, options) {
if (options == null) { options = {}; }
if (options == null) {
options = {};
}
options.Subtype = 'Text';
options.Contents = new String(contents);
options.Name = 'Comment';
if (options.color == null) { options.color = [243, 223, 92]; }
if (options.color == null) {
options.color = [243, 223, 92];
}
return this.annotate(x, y, w, h, options);
},
link(x, y, w, h, url, options) {
if (options == null) { options = {}; }
if (options == null) {
options = {};
}
options.Subtype = 'Link';
if (typeof url === 'number') {
// Link to a page in the document (the page must already exist)
const pages = this._root.data.Pages.data;
if ((url >= 0) && (url < pages.Kids.length)) {
if (url >= 0 && url < pages.Kids.length) {
options.A = this.ref({
S: 'GoTo',
D: [pages.Kids[url], 'XYZ', null, null, null]});
D: [pages.Kids[url], 'XYZ', null, null, null]
});
options.A.end();
} else {
throw new Error(`The document has no page ${url}`);
}
} else {
// Link to an external url
options.A = this.ref({
@ -60,77 +70,95 @@ export default {
},
_markup(x, y, w, h, options) {
if (options == null) { options = {}; }
if (options == null) {
options = {};
}
const [x1, y1, x2, y2] = this._convertRect(x, y, w, h);
options.QuadPoints = [x1, y2, x2, y2, x1, y1, x2, y1];
options.Contents = new String;
options.Contents = new String();
return this.annotate(x, y, w, h, options);
},
highlight(x, y, w, h, options) {
if (options == null) { options = {}; }
if (options == null) {
options = {};
}
options.Subtype = 'Highlight';
if (options.color == null) { options.color = [241, 238, 148]; }
if (options.color == null) {
options.color = [241, 238, 148];
}
return this._markup(x, y, w, h, options);
},
underline(x, y, w, h, options) {
if (options == null) { options = {}; }
if (options == null) {
options = {};
}
options.Subtype = 'Underline';
return this._markup(x, y, w, h, options);
},
strike(x, y, w, h, options) {
if (options == null) { options = {}; }
if (options == null) {
options = {};
}
options.Subtype = 'StrikeOut';
return this._markup(x, y, w, h, options);
},
lineAnnotation(x1, y1, x2, y2, options) {
if (options == null) { options = {}; }
if (options == null) {
options = {};
}
options.Subtype = 'Line';
options.Contents = new String;
options.Contents = new String();
options.L = [x1, this.page.height - y1, x2, this.page.height - y2];
return this.annotate(x1, y1, x2, y2, options);
},
rectAnnotation(x, y, w, h, options) {
if (options == null) { options = {}; }
if (options == null) {
options = {};
}
options.Subtype = 'Square';
options.Contents = new String;
options.Contents = new String();
return this.annotate(x, y, w, h, options);
},
ellipseAnnotation(x, y, w, h, options) {
if (options == null) { options = {}; }
if (options == null) {
options = {};
}
options.Subtype = 'Circle';
options.Contents = new String;
options.Contents = new String();
return this.annotate(x, y, w, h, options);
},
textAnnotation(x, y, w, h, text, options) {
if (options == null) { options = {}; }
if (options == null) {
options = {};
}
options.Subtype = 'FreeText';
options.Contents = new String(text);
options.DA = new String;
options.DA = new String();
return this.annotate(x, y, w, h, options);
},
_convertRect(x1, y1, w, h) {
// flip y1 and y2
let y2 = y1;
y1 += h;
// make x2
let x2 = x1 + w;
// apply current transformation matrix to points
const [m0, m1, m2, m3, m4, m5] = this._ctm;
x1 = (m0 * x1) + (m2 * y1) + m4;
y1 = (m1 * x1) + (m3 * y1) + m5;
x2 = (m0 * x2) + (m2 * y2) + m4;
y2 = (m1 * x2) + (m3 * y2) + m5;
x1 = m0 * x1 + m2 * y1 + m4;
y1 = m1 * x1 + m3 * y1 + m5;
x2 = m0 * x2 + m2 * y2 + m4;
y2 = m1 * x2 + m3 * y2 + m5;
return [x1, y1, x2, y2];
}
};

View File

@ -1,40 +1,40 @@
import Gradient from '../gradient';
const {
PDFGradient,
PDFLinearGradient,
PDFRadialGradient
} = Gradient;
const { PDFGradient, PDFLinearGradient, PDFRadialGradient } = Gradient;
export default {
initColor() {
// The opacity dictionaries
this._opacityRegistry = {};
this._opacityCount = 0;
return this._gradCount = 0;
return (this._gradCount = 0);
},
_normalizeColor(color) {
_normalizeColor(color) {
if (color instanceof PDFGradient) {
return color;
}
if (typeof color === 'string') {
if (color.charAt(0) === '#') {
if (color.length === 4) { color = color.replace(/#([0-9A-F])([0-9A-F])([0-9A-F])/i, "#$1$1$2$2$3$3"); }
if (color.length === 4) {
color = color.replace(
/#([0-9A-F])([0-9A-F])([0-9A-F])/i,
'#$1$1$2$2$3$3'
);
}
const hex = parseInt(color.slice(1), 16);
color = [hex >> 16, (hex >> 8) & 0xff, hex & 0xff];
} else if (namedColors[color]) {
color = namedColors[color];
}
}
if (Array.isArray(color)) {
if (Array.isArray(color)) {
// RGB
if (color.length === 3) {
color = color.map(part => part / 255);
// CMYK
// CMYK
} else if (color.length === 4) {
color = color.map(part => part / 100);
}
@ -46,7 +46,9 @@ export default {
_setColor(color, stroke) {
color = this._normalizeColor(color);
if (!color) { return false; }
if (!color) {
return false;
}
const op = stroke ? 'SCN' : 'scn';
@ -71,7 +73,9 @@ export default {
fillColor(color, opacity) {
const set = this._setColor(color, false);
if (set) { this.fillOpacity(opacity); }
if (set) {
this.fillOpacity(opacity);
}
// save this for text wrapper, which needs to reset
// the fill color on new pages
@ -81,52 +85,63 @@ export default {
strokeColor(color, opacity) {
const set = this._setColor(color, true);
if (set) { this.strokeOpacity(opacity); }
if (set) {
this.strokeOpacity(opacity);
}
return this;
},
opacity(opacity) {
this._doOpacity(opacity, opacity);
return this;
},
this._doOpacity(opacity, opacity);
return this;
},
fillOpacity(opacity) {
this._doOpacity(opacity, null);
return this;
},
this._doOpacity(opacity, null);
return this;
},
strokeOpacity(opacity) {
this._doOpacity(null, opacity);
return this;
},
this._doOpacity(null, opacity);
return this;
},
_doOpacity(fillOpacity, strokeOpacity) {
let dictionary, name;
if ((fillOpacity == null) && (strokeOpacity == null)) { return; }
let dictionary, name;
if (fillOpacity == null && strokeOpacity == null) {
return;
}
if (fillOpacity != null) { fillOpacity = Math.max(0, Math.min(1, fillOpacity)); }
if (strokeOpacity != null) { strokeOpacity = Math.max(0, Math.min(1, strokeOpacity)); }
const key = `${fillOpacity}_${strokeOpacity}`;
if (fillOpacity != null) {
fillOpacity = Math.max(0, Math.min(1, fillOpacity));
}
if (strokeOpacity != null) {
strokeOpacity = Math.max(0, Math.min(1, strokeOpacity));
}
const key = `${fillOpacity}_${strokeOpacity}`;
if (this._opacityRegistry[key]) {
[dictionary, name] = this._opacityRegistry[key];
} else {
dictionary =
{Type: 'ExtGState'};
if (this._opacityRegistry[key]) {
[dictionary, name] = this._opacityRegistry[key];
} else {
dictionary = { Type: 'ExtGState' };
if (fillOpacity != null) { dictionary.ca = fillOpacity; }
if (strokeOpacity != null) { dictionary.CA = strokeOpacity; }
if (fillOpacity != null) {
dictionary.ca = fillOpacity;
}
if (strokeOpacity != null) {
dictionary.CA = strokeOpacity;
}
dictionary = this.ref(dictionary);
dictionary.end();
const id = ++this._opacityCount;
name = `Gs${id}`;
this._opacityRegistry[key] = [dictionary, name];
}
dictionary = this.ref(dictionary);
dictionary.end();
const id = ++this._opacityCount;
name = `Gs${id}`;
this._opacityRegistry[key] = [dictionary, name];
}
this.page.ext_gstates[name] = dictionary;
return this.addContent(`/${name} gs`);
},
this.page.ext_gstates[name] = dictionary;
return this.addContent(`/${name} gs`);
},
linearGradient(x1, y1, x2, y2) {
return new PDFLinearGradient(this, x1, y1, x2, y2);

View File

@ -24,18 +24,22 @@ export default {
}
// check registered fonts if src is a string
if ((typeof src === 'string') && this._registeredFonts[src]) {
if (typeof src === 'string' && this._registeredFonts[src]) {
cacheKey = src;
({src, family} = this._registeredFonts[src]);
({ src, family } = this._registeredFonts[src]);
} else {
cacheKey = family || src;
if (typeof cacheKey !== 'string') { cacheKey = null; }
if (typeof cacheKey !== 'string') {
cacheKey = null;
}
}
if (size != null) { this.fontSize(size); }
if (size != null) {
this.fontSize(size);
}
// fast path: check if the font is already in the PDF
if (font = this._fontFamilies[cacheKey]) {
if ((font = this._fontFamilies[cacheKey])) {
this._font = font;
return this;
}
@ -46,7 +50,7 @@ export default {
// check for existing font familes with the same name already in the PDF
// useful if the font was passed as a buffer
if (font = this._fontFamilies[this._font.name]) {
if ((font = this._fontFamilies[this._font.name])) {
this._font = font;
return this;
}
@ -69,7 +73,9 @@ export default {
},
currentLineHeight(includeGap) {
if (includeGap == null) { includeGap = false; }
if (includeGap == null) {
includeGap = false;
}
return this._font.lineHeight(this._fontSize, includeGap);
},

View File

@ -3,12 +3,14 @@ import PDFImage from '../image';
export default {
initImages() {
this._imageRegistry = {};
return this._imageCount = 0;
return (this._imageCount = 0);
},
image(src, x, y, options) {
let bh, bp, bw, image, ip, left, left1;
if (options == null) { options = {}; }
if (options == null) {
options = {};
}
if (typeof x === 'object') {
options = x;
x = null;
@ -33,7 +35,9 @@ export default {
image.embed(this);
}
if (this.page.xobjects[image.label] == null) { this.page.xobjects[image.label] = image.obj; }
if (this.page.xobjects[image.label] == null) {
this.page.xobjects[image.label] = image.obj;
}
let w = options.width || image.width;
let h = options.height || image.height;
@ -42,16 +46,13 @@ export default {
const wp = w / image.width;
w = image.width * wp;
h = image.height * wp;
} else if (options.height && !options.width) {
const hp = h / image.height;
w = image.width * hp;
h = image.height * hp;
} else if (options.scale) {
w = image.width * options.scale;
h = image.height * options.scale;
} else if (options.fit) {
[bw, bh] = options.fit;
bp = bw / bh;
@ -63,7 +64,6 @@ export default {
h = bh;
w = bh * ip;
}
} else if (options.cover) {
[bw, bh] = options.cover;
bp = bw / bh;
@ -79,20 +79,22 @@ export default {
if (options.fit || options.cover) {
if (options.align === 'center') {
x = (x + (bw / 2)) - (w / 2);
x = x + bw / 2 - w / 2;
} else if (options.align === 'right') {
x = (x + bw) - w;
x = x + bw - w;
}
if (options.valign === 'center') {
y = (y + (bh / 2)) - (h / 2);
y = y + bh / 2 - h / 2;
} else if (options.valign === 'bottom') {
y = (y + bh) - h;
y = y + bh - h;
}
}
// Set the current y position to below the image if it is in the document flow
if (this.y === y) { this.y += h; }
// Set the current y position to below the image if it is in the document flow
if (this.y === y) {
this.y += h;
}
this.save();
this.transform(w, 0, 0, -h, x, y + h);

View File

@ -1,15 +1,15 @@
import PDFOutline from '../outline';
export default {
initOutline() {
return this.outline = new PDFOutline(this, null, null, null);
},
initOutline() {
return (this.outline = new PDFOutline(this, null, null, null));
},
endOutline() {
this.outline.endOutline();
if (this.outline.children.length > 0) {
this._root.data.Outlines = this.outline.dictionary;
return this._root.data.PageMode = 'UseOutlines';
}
endOutline() {
this.outline.endOutline();
if (this.outline.children.length > 0) {
this._root.data.Outlines = this.outline.dictionary;
return (this._root.data.PageMode = 'UseOutlines');
}
}
};

View File

@ -1,9 +1,7 @@
import LineWrapper from '../line_wrapper';
import PDFObject from '../object';
const {
number
} = PDFObject;
const { number } = PDFObject;
export default {
initText() {
@ -11,7 +9,7 @@ export default {
// Current coordinates
this.x = 0;
this.y = 0;
return this._lineGap = 0;
return (this._lineGap = 0);
},
lineGap(_lineGap) {
@ -20,14 +18,18 @@ export default {
},
moveDown(lines) {
if (lines == null) { lines = 1; }
this.y += (this.currentLineHeight(true) * lines) + this._lineGap;
if (lines == null) {
lines = 1;
}
this.y += this.currentLineHeight(true) * lines + this._lineGap;
return this;
},
moveUp(lines) {
if (lines == null) { lines = 1; }
this.y -= (this.currentLineHeight(true) * lines) + this._lineGap;
if (lines == null) {
lines = 1;
}
this.y -= this.currentLineHeight(true) * lines + this._lineGap;
return this;
},
@ -35,7 +37,7 @@ export default {
options = this._initOptions(x, y, options);
// Convert text to a string
text = (text == null) ? '' : `${text}`;
text = text == null ? '' : `${text}`;
// if the wordSpacing option is specified, remove multiple consecutive spaces
if (options.wordSpacing) {
@ -54,9 +56,11 @@ export default {
this._textOptions = options.continued ? options : null;
wrapper.wrap(text, options);
// render paragraphs as single lines
// render paragraphs as single lines
} else {
for (let line of text.split('\n')) { lineCallback(line, options); }
for (let line of text.split('\n')) {
lineCallback(line, options);
}
}
return this;
@ -67,20 +71,27 @@ export default {
},
widthOfString(string, options) {
if (options == null) { options = {}; }
return this._font.widthOfString(string, this._fontSize, options.features) + ((options.characterSpacing || 0) * (string.length - 1));
if (options == null) {
options = {};
}
return (
this._font.widthOfString(string, this._fontSize, options.features) +
(options.characterSpacing || 0) * (string.length - 1)
);
},
heightOfString(text, options) {
if (options == null) { options = {}; }
const {x,y} = this;
if (options == null) {
options = {};
}
const { x, y } = this;
options = this._initOptions(options);
options.height = Infinity; // don't break pages
const lineGap = options.lineGap || this._lineGap || 0;
this._text(text, this.x, this.y, options, (line, options) => {
return this.y += this.currentLineHeight(true) + lineGap;
return (this.y += this.currentLineHeight(true) + lineGap);
});
const height = this.y - y;
@ -94,11 +105,13 @@ export default {
options = this._initOptions(x, y, options);
const listType = options.listType || 'bullet';
const unit = Math.round(((this._font.ascender / 1000) * this._fontSize));
const unit = Math.round((this._font.ascender / 1000) * this._fontSize);
const midLine = unit / 2;
const r = options.bulletRadius || (unit / 3);
const indent = options.textIndent || (listType === 'bullet' ? r * 5 : unit * 2);
const itemIndent = options.bulletIndent || (listType === 'bullet' ? r * 8 : unit * 2);
const r = options.bulletRadius || unit / 3;
const indent =
options.textIndent || (listType === 'bullet' ? r * 5 : unit * 2);
const itemIndent =
options.bulletIndent || (listType === 'bullet' ? r * 8 : unit * 2);
let level = 1;
const items = [];
@ -131,7 +144,7 @@ export default {
return `${n}.`;
case 'lettered':
var letter = String.fromCharCode(((n - 1) % 26) + 65);
var times = Math.floor(((n - 1) / 26) + 1);
var times = Math.floor((n - 1) / 26 + 1);
var text = Array(times + 1).join(letter);
return `${text}.`;
}
@ -153,24 +166,25 @@ export default {
switch (listType) {
case 'bullet':
this.circle((this.x - indent) + r, this.y + midLine, r);
this.circle(this.x - indent + r, this.y + midLine, r);
return this.fill();
case 'numbered': case 'lettered':
case 'numbered':
case 'lettered':
var text = label(numbers[i - 1]);
return this._fragment(text, this.x - indent, this.y, options);
}
});
wrapper.on('sectionStart', () => {
const pos = indent + (itemIndent * (level - 1));
const pos = indent + itemIndent * (level - 1);
this.x += pos;
return wrapper.lineWidth -= pos;
return (wrapper.lineWidth -= pos);
});
wrapper.on('sectionEnd', () => {
const pos = indent + (itemIndent * (level - 1));
const pos = indent + itemIndent * (level - 1);
this.x -= pos;
return wrapper.lineWidth += pos;
return (wrapper.lineWidth += pos);
});
wrapper.wrap(items.join('\n'), options);
@ -179,8 +193,12 @@ export default {
},
_initOptions(x, y, options) {
if (x == null) { x = {}; }
if (options == null) { options = {}; }
if (x == null) {
x = {};
}
if (options == null) {
options = {};
}
if (typeof x === 'object') {
options = x;
x = null;
@ -189,7 +207,10 @@ export default {
// clone options object
options = (function() {
const opts = {};
for (let k in options) { const v = options[k]; opts[k] = v; }
for (let k in options) {
const v = options[k];
opts[k] = v;
}
return opts;
})();
@ -198,7 +219,9 @@ export default {
for (let key in this._textOptions) {
const val = this._textOptions[key];
if (key !== 'continued') {
if (options[key] == null) { options[key] = val; }
if (options[key] == null) {
options[key] = val;
}
}
}
}
@ -213,31 +236,41 @@ export default {
// wrap to margins if no x or y position passed
if (options.lineBreak !== false) {
if (options.width == null) { options.width = this.page.width - this.x - this.page.margins.right; }
if (options.width == null) {
options.width = this.page.width - this.x - this.page.margins.right;
}
}
if (!options.columns) { options.columns = 0; }
if (options.columnGap == null) { options.columnGap = 18; } // 1/4 inch
if (!options.columns) {
options.columns = 0;
}
if (options.columnGap == null) {
options.columnGap = 18;
} // 1/4 inch
return options;
},
_line(text, options, wrapper) {
if (options == null) { options = {}; }
if (options == null) {
options = {};
}
this._fragment(text, this.x, this.y, options);
const lineGap = options.lineGap || this._lineGap || 0;
if (!wrapper) {
return this.x += this.widthOfString(text);
return (this.x += this.widthOfString(text));
} else {
return this.y += this.currentLineHeight(true) + lineGap;
return (this.y += this.currentLineHeight(true) + lineGap);
}
},
_fragment(text, x, y, options) {
let dy, encoded, i, positions, textWidth, words;
text = (`${text}`).replace(/\n/g, '');
if (text.length === 0) { return; }
text = `${text}`.replace(/\n/g, '');
if (text.length === 0) {
return;
}
// handle options
const align = options.align || 'left';
@ -253,7 +286,7 @@ export default {
break;
case 'center':
x += (options.lineWidth / 2) - (options.textWidth / 2);
x += options.lineWidth / 2 - options.textWidth / 2;
break;
case 'justify':
@ -261,7 +294,11 @@ export default {
words = text.trim().split(/\s+/);
textWidth = this.widthOfString(text.replace(/\s+/g, ''), options);
var spaceWidth = this.widthOfString(' ') + characterSpacing;
wordSpacing = Math.max(0, ((options.lineWidth - textWidth) / Math.max(1, words.length - 1)) - spaceWidth);
wordSpacing = Math.max(
0,
(options.lineWidth - textWidth) / Math.max(1, words.length - 1) -
spaceWidth
);
break;
}
}
@ -274,10 +311,12 @@ export default {
case 'svg-middle':
dy = 0.5 * this._font.xHeight;
break;
case 'middle': case 'svg-central':
case 'middle':
case 'svg-central':
dy = 0.5 * (this._font.descender + this._font.ascender);
break;
case 'bottom': case 'ideographic':
case 'bottom':
case 'ideographic':
dy = this._font.descender;
break;
case 'alphabetic':
@ -299,7 +338,10 @@ export default {
}
// calculate the actual rendered width of the string after word and character spacing
const renderedWidth = options.textWidth + (wordSpacing * (options.wordCount - 1)) + (characterSpacing * (text.length - 1));
const renderedWidth =
options.textWidth +
wordSpacing * (options.wordCount - 1) +
characterSpacing * (text.length - 1);
// create link annotations if the link option is given
if (options.link != null) {
@ -309,14 +351,19 @@ export default {
// create underline or strikethrough line
if (options.underline || options.strike) {
this.save();
if (!options.stroke) { this.strokeColor(...(this._fillColor || [])); }
if (!options.stroke) {
this.strokeColor(...(this._fillColor || []));
}
const lineWidth = this._fontSize < 10 ? 0.5 : Math.floor(this._fontSize / 10);
const lineWidth =
this._fontSize < 10 ? 0.5 : Math.floor(this._fontSize / 10);
this.lineWidth(lineWidth);
const d = options.underline ? 1 : 2;
let lineY = y + (this.currentLineHeight() / d);
if (options.underline) { lineY -= lineWidth; }
let lineY = y + this.currentLineHeight() / d;
if (options.underline) {
lineY -= lineWidth;
}
this.moveTo(x, lineY);
this.lineTo(x + renderedWidth, lineY);
@ -344,10 +391,12 @@ export default {
y = this.page.height - y - dy;
// add current font to page if necessary
if (this.page.fonts[this._font.id] == null) { this.page.fonts[this._font.id] = this._font.ref(); }
if (this.page.fonts[this._font.id] == null) {
this.page.fonts[this._font.id] = this._font.ref();
}
// begin the text object
this.addContent("BT");
this.addContent('BT');
// text position
this.addContent(`1 0 0 1 ${number(x)} ${number(y)} Tm`);
@ -357,10 +406,14 @@ export default {
// rendering mode
const mode = options.fill && options.stroke ? 2 : options.stroke ? 1 : 0;
if (mode) { this.addContent(`${mode} Tr`); }
if (mode) {
this.addContent(`${mode} Tr`);
}
// Character spacing
if (characterSpacing) { this.addContent(`${number(characterSpacing)} Tc`); }
if (characterSpacing) {
this.addContent(`${number(characterSpacing)} Tc`);
}
// Add the actual text
// If we have a word spacing value, we need to encode each word separately
@ -374,7 +427,10 @@ export default {
encoded = [];
positions = [];
for (let word of words) {
const [encodedWord, positionsWord] = this._font.encode(word, options.features);
const [encodedWord, positionsWord] = this._font.encode(
word,
options.features
);
encoded.push(...(encodedWord || []));
positions.push(...(positionsWord || []));
@ -382,7 +438,10 @@ export default {
// clone object because of cache
const space = {};
const object = positions[positions.length - 1];
for (let key in object) { const val = object[key]; space[key] = val; }
for (let key in object) {
const val = object[key];
space[key] = val;
}
space.xAdvance += wordSpacing;
positions[positions.length - 1] = space;
}
@ -399,11 +458,12 @@ export default {
const addSegment = cur => {
if (last < cur) {
const hex = encoded.slice(last, cur).join('');
const advance = positions[cur - 1].xAdvance - positions[cur - 1].advanceWidth;
const advance =
positions[cur - 1].xAdvance - positions[cur - 1].advanceWidth;
commands.push(`<${hex}> ${number(-advance)}`);
}
return last = cur;
return (last = cur);
};
// Flushes the current TJ commands to the output stream
@ -412,7 +472,7 @@ export default {
if (commands.length > 0) {
this.addContent(`[${commands.join(' ')}] TJ`);
return commands.length = 0;
return (commands.length = 0);
}
};
@ -425,7 +485,11 @@ export default {
flush(i);
// Move the text position and flush just the current character
this.addContent(`1 0 0 1 ${number(x + (pos.xOffset * scale))} ${number(y + (pos.yOffset * scale))} Tm`);
this.addContent(
`1 0 0 1 ${number(x + pos.xOffset * scale)} ${number(
y + pos.yOffset * scale
)} Tm`
);
flush(i + 1);
hadOffset = true;
@ -437,7 +501,7 @@ export default {
}
// Group segments that don't have any advance adjustments
if ((pos.xAdvance - pos.advanceWidth) !== 0) {
if (pos.xAdvance - pos.advanceWidth !== 0) {
addSegment(i + 1);
}
}
@ -449,7 +513,7 @@ export default {
flush(i);
// end the text object
this.addContent("ET");
this.addContent('ET');
// restore flipped coordinate system
return this.restore();

View File

@ -1,9 +1,7 @@
import SVGPath from '../path';
import PDFObject from '../object';
const {
number
} = PDFObject;
const { number } = PDFObject;
// This constant is used to approximate a symmetrical arc using a cubic
// Bezier curve.
@ -11,7 +9,7 @@ const KAPPA = 4.0 * ((Math.sqrt(2) - 1.0) / 3.0);
export default {
initVector() {
this._ctm = [1, 0, 0, 1, 0, 0]; // current transformation matrix
return this._ctmStack = [];
return (this._ctmStack = []);
},
save() {
@ -40,7 +38,9 @@ export default {
},
lineCap(c) {
if (typeof c === 'string') { c = this._CAP_STYLES[c.toUpperCase()]; }
if (typeof c === 'string') {
c = this._CAP_STYLES[c.toUpperCase()];
}
return this.addContent(`${c} J`);
},
@ -51,7 +51,9 @@ export default {
},
lineJoin(j) {
if (typeof j === 'string') { j = this._JOIN_STYLES[j.toUpperCase()]; }
if (typeof j === 'string') {
j = this._JOIN_STYLES[j.toUpperCase()];
}
return this.addContent(`${j} j`);
},
@ -61,21 +63,27 @@ export default {
dash(length, options) {
let phase;
if (options == null) { options = {}; }
if (length == null) { return this; }
if (options == null) {
options = {};
}
if (length == null) {
return this;
}
if (Array.isArray(length)) {
length = length.map((v) => number(v)).join(' ');
length = length.map(v => number(v)).join(' ');
phase = options.phase || 0;
return this.addContent(`[${length}] ${number(phase)} d`);
} else {
const space = options.space != null ? options.space : length;
phase = options.phase || 0;
return this.addContent(`[${number(length)} ${number(space)}] ${number(phase)} d`);
return this.addContent(
`[${number(length)} ${number(space)}] ${number(phase)} d`
);
}
},
undash() {
return this.addContent("[] 0 d");
return this.addContent('[] 0 d');
},
moveTo(x, y) {
@ -87,31 +95,41 @@ export default {
},
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) {
return this.addContent(`${number(cp1x)} ${number(cp1y)} ${number(cp2x)} ${number(cp2y)} ${number(x)} ${number(y)} c`);
return this.addContent(
`${number(cp1x)} ${number(cp1y)} ${number(cp2x)} ${number(cp2y)} ${number(
x
)} ${number(y)} c`
);
},
quadraticCurveTo(cpx, cpy, x, y) {
return this.addContent(`${number(cpx)} ${number(cpy)} ${number(x)} ${number(y)} v`);
return this.addContent(
`${number(cpx)} ${number(cpy)} ${number(x)} ${number(y)} v`
);
},
rect(x, y, w, h) {
return this.addContent(`${number(x)} ${number(y)} ${number(w)} ${number(h)} re`);
return this.addContent(
`${number(x)} ${number(y)} ${number(w)} ${number(h)} re`
);
},
roundedRect(x, y, w, h, r) {
if (r == null) { r = 0; }
if (r == null) {
r = 0;
}
r = Math.min(r, 0.5 * w, 0.5 * h);
// amount to inset control points from corners (see `ellipse`)
const c = r * (1.0 - KAPPA);
this.moveTo(x + r, y);
this.lineTo((x + w) - r, y);
this.bezierCurveTo((x + w) - c, y, x + w, y + c, x + w, y + r);
this.lineTo(x + w, (y + h) - r);
this.bezierCurveTo(x + w, (y + h) - c, (x + w) - c, y + h, (x + w) - r, y + h);
this.lineTo(x + w - r, y);
this.bezierCurveTo(x + w - c, y, x + w, y + c, x + w, y + r);
this.lineTo(x + w, y + h - r);
this.bezierCurveTo(x + w, y + h - c, x + w - c, y + h, x + w - r, y + h);
this.lineTo(x + r, y + h);
this.bezierCurveTo(x + c, y + h, x, (y + h) - c, x, (y + h) - r);
this.bezierCurveTo(x + c, y + h, x, y + h - c, x, y + h - r);
this.lineTo(x, y + r);
this.bezierCurveTo(x, y + c, x + c, y, x + r, y);
return this.closePath();
@ -119,13 +137,15 @@ export default {
ellipse(x, y, r1, r2) {
// based on http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas/2173084#2173084
if (r2 == null) { r2 = r1; }
if (r2 == null) {
r2 = r1;
}
x -= r1;
y -= r2;
const ox = r1 * KAPPA;
const oy = r2 * KAPPA;
const xe = x + (r1 * 2);
const ye = y + (r2 * 2);
const xe = x + r1 * 2;
const ye = y + r2 * 2;
const xm = x + r1;
const ym = y + r2;
@ -142,7 +162,9 @@ export default {
},
arc(x, y, radius, startAngle, endAngle, anticlockwise) {
if (anticlockwise == null) { anticlockwise = false; }
if (anticlockwise == null) {
anticlockwise = false;
}
const TWO_PI = 2.0 * Math.PI;
const HALF_PI = 0.5 * Math.PI;
@ -151,11 +173,10 @@ export default {
if (Math.abs(deltaAng) > TWO_PI) {
// draw only full circle if more than that is specified
deltaAng = TWO_PI;
} else if ((deltaAng !== 0) && (anticlockwise !== (deltaAng < 0))) {
} else if (deltaAng !== 0 && anticlockwise !== deltaAng < 0) {
// necessary to flip direction of rendering
const dir = anticlockwise ? -1 : 1;
deltaAng = (dir * TWO_PI) + deltaAng;
deltaAng = dir * TWO_PI + deltaAng;
}
const numSegs = Math.ceil(Math.abs(deltaAng) / HALF_PI);
@ -168,13 +189,17 @@ export default {
let deltaCy = Math.cos(curAng) * handleLen;
// anchor point
let ax = x + (Math.cos(curAng) * radius);
let ay = y + (Math.sin(curAng) * radius);
let ax = x + Math.cos(curAng) * radius;
let ay = y + Math.sin(curAng) * radius;
// calculate and render segments
this.moveTo(ax, ay);
for (let segIdx = 0, end = numSegs, asc = 0 <= end; asc ? segIdx < end : segIdx > end; asc ? segIdx++ : segIdx--) {
for (
let segIdx = 0, end = numSegs, asc = 0 <= end;
asc ? segIdx < end : segIdx > end;
asc ? segIdx++ : segIdx--
) {
// starting control point
const cp1x = ax + deltaCx;
const cp1y = ay + deltaCy;
@ -183,8 +208,8 @@ export default {
curAng += segAng;
// next anchor point
ax = x + (Math.cos(curAng) * radius);
ay = y + (Math.sin(curAng) * radius);
ax = x + Math.cos(curAng) * radius;
ay = y + Math.sin(curAng) * radius;
// next control point delta
deltaCx = -Math.sin(curAng) * handleLen;
@ -203,7 +228,9 @@ export default {
polygon(...points) {
this.moveTo(...(points.shift() || []));
for (let point of points) { this.lineTo(...(point || [])); }
for (let point of points) {
this.lineTo(...(point || []));
}
return this.closePath();
},
@ -226,17 +253,23 @@ export default {
color = null;
}
if (color) { this.fillColor(color); }
if (color) {
this.fillColor(color);
}
return this.addContent(`f${this._windingRule(rule)}`);
},
stroke(color) {
if (color) { this.strokeColor(color); }
if (color) {
this.strokeColor(color);
}
return this.addContent('S');
},
fillAndStroke(fillColor, strokeColor, rule) {
if (strokeColor == null) { strokeColor = fillColor; }
if (strokeColor == null) {
strokeColor = fillColor;
}
const isFillRule = /(even-?odd)|(non-?zero)/;
if (isFillRule.test(fillColor)) {
rule = fillColor;
@ -264,14 +297,14 @@ export default {
// keep track of the current transformation matrix
const m = this._ctm;
const [m0, m1, m2, m3, m4, m5] = m;
m[0] = (m0 * m11) + (m2 * m12);
m[1] = (m1 * m11) + (m3 * m12);
m[2] = (m0 * m21) + (m2 * m22);
m[3] = (m1 * m21) + (m3 * m22);
m[4] = (m0 * dx) + (m2 * dy) + m4;
m[5] = (m1 * dx) + (m3 * dy) + m5;
m[0] = m0 * m11 + m2 * m12;
m[1] = m1 * m11 + m3 * m12;
m[2] = m0 * m21 + m2 * m22;
m[3] = m1 * m21 + m3 * m22;
m[4] = m0 * dx + m2 * dy + m4;
m[5] = m1 * dx + m3 * dy + m5;
const values = ([m11, m12, m21, m22, dx, dy].map((v) => number(v))).join(' ');
const values = [m11, m12, m21, m22, dx, dy].map(v => number(v)).join(' ');
return this.addContent(`${values} cm`);
},
@ -281,7 +314,9 @@ export default {
rotate(angle, options) {
let y;
if (options == null) { options = {}; }
if (options == null) {
options = {};
}
const rad = (angle * Math.PI) / 180;
const cos = Math.cos(rad);
const sin = Math.sin(rad);
@ -289,8 +324,8 @@ export default {
if (options.origin != null) {
[x, y] = options.origin;
const x1 = (x * cos) - (y * sin);
const y1 = (x * sin) + (y * cos);
const x1 = x * cos - y * sin;
const y1 = x * sin + y * cos;
x -= x1;
y -= y1;
}
@ -300,9 +335,13 @@ export default {
scale(xFactor, yFactor, options) {
let y;
if (yFactor == null) { yFactor = xFactor; }
if (options == null) { options = {}; }
if (typeof yFactor === "object") {
if (yFactor == null) {
yFactor = xFactor;
}
if (options == null) {
options = {};
}
if (typeof yFactor === 'object') {
options = yFactor;
yFactor = xFactor;
}

View File

@ -23,12 +23,12 @@ const escapable = {
const swapBytes = function(buff) {
const l = buff.length;
if (l & 0x01) {
throw new Error("Buffer length must be even");
throw new Error('Buffer length must be even');
} else {
for (let i = 0, end = l - 1; i < end; i += 2) {
const a = buff[i];
buff[i] = buff[i + 1];
buff[i+1] = a;
buff[i + 1] = a;
}
}
@ -41,7 +41,7 @@ class PDFObject {
if (typeof object === 'string') {
return `/${object}`;
// String objects are converted to PDF strings (UTF-16)
// String objects are converted to PDF strings (UTF-16)
} else if (object instanceof String) {
let string = object;
// Detect if this is a unicode string
@ -73,20 +73,20 @@ class PDFObject {
return `(${string})`;
// Buffers are converted to PDF hex strings
// Buffers are converted to PDF hex strings
} else if (Buffer.isBuffer(object)) {
return `<${object.toString('hex')}>`;
} else if (object instanceof PDFAbstractReference) {
return object.toString();
} else if (object instanceof Date) {
let string = `D:${pad(object.getUTCFullYear(), 4)}` +
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';
pad(object.getUTCSeconds(), 2) +
'Z';
// Encrypt the string when necessary
if (encryptFn) {
@ -97,11 +97,9 @@ class PDFObject {
}
return `(${string})`;
} else if (Array.isArray(object)) {
const items = (object.map((e) => PDFObject.convert(e, encryptFn))).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) {
@ -111,22 +109,20 @@ class PDFObject {
out.push('>>');
return out.join('\n');
} else if (typeof object === 'number') {
return PDFObject.number(object);
} else {
return `${object}`;
}
}
static number(n) {
if ((n > -1e21) && (n < 1e21)) {
if (n > -1e21 && n < 1e21) {
return Math.round(n * 1e6) / 1e6;
}
throw new Error(`unsupported number: ${n}`);
}
};
}
export default PDFObject;

View File

@ -1,7 +1,9 @@
class PDFOutline {
constructor(document, parent, title, dest, options) {
this.document = document;
if (options == null) { options = { expanded: false }; }
if (options == null) {
options = { expanded: false };
}
this.options = options;
this.outlineData = {};
@ -22,8 +24,16 @@ class PDFOutline {
}
addItem(title, options) {
if (options == null) { options = { expanded: false }; }
const result = new PDFOutline(this.document, this.dictionary, title, this.document.page, options);
if (options == null) {
options = { expanded: false };
}
const result = new PDFOutline(
this.document,
this.dictionary,
title,
this.document.page,
options
);
this.children.push(result);
return result;
@ -37,17 +47,22 @@ class PDFOutline {
this.outlineData.Count = this.children.length;
}
const first = this.children[0], last = this.children[this.children.length - 1];
const first = this.children[0],
last = this.children[this.children.length - 1];
this.outlineData.First = first.dictionary;
this.outlineData.Last = last.dictionary;
for (i = 0, end = this.children.length, asc = 0 <= end; asc ? i < end : i > end; asc ? i++ : i--) {
for (
i = 0, end = this.children.length, asc = 0 <= end;
asc ? i < end : i > end;
asc ? i++ : i--
) {
const child = this.children[i];
if (i > 0) {
child.outlineData.Prev = this.children[i-1].dictionary;
child.outlineData.Prev = this.children[i - 1].dictionary;
}
if (i < (this.children.length - 1)) {
child.outlineData.Next = this.children[i+1].dictionary;
if (i < this.children.length - 1) {
child.outlineData.Next = this.children[i + 1].dictionary;
}
child.endOutline();
}
@ -57,5 +72,4 @@ class PDFOutline {
}
}
export default PDFOutline;
export default PDFOutline;

View File

@ -3,7 +3,7 @@ PDFPage - represents a single page in the PDF document
By Devon Govett
*/
const DEFAULT_MARGINS = {
const DEFAULT_MARGINS = {
top: 72,
left: 72,
bottom: 72,
@ -21,16 +21,16 @@ const SIZES = {
A5: [419.53, 595.28],
A6: [297.64, 419.53],
A7: [209.76, 297.64],
A8: [147.40, 209.76],
A9: [104.88, 147.40],
A10: [73.70, 104.88],
A8: [147.4, 209.76],
A9: [104.88, 147.4],
A10: [73.7, 104.88],
B0: [2834.65, 4008.19],
B1: [2004.09, 2834.65],
B2: [1417.32, 2004.09],
B3: [1000.63, 1417.32],
B4: [708.66, 1000.63],
B5: [498.90, 708.66],
B6: [354.33, 498.90],
B5: [498.9, 708.66],
B6: [354.33, 498.9],
B7: [249.45, 354.33],
B8: [175.75, 249.45],
B9: [124.72, 175.75],
@ -46,55 +46,60 @@ const SIZES = {
C8: [161.57, 229.61],
C9: [113.39, 161.57],
C10: [79.37, 113.39],
RA0: [2437.80, 3458.27],
RA1: [1729.13, 2437.80],
RA2: [1218.90, 1729.13],
RA3: [864.57, 1218.90],
RA0: [2437.8, 3458.27],
RA1: [1729.13, 2437.8],
RA2: [1218.9, 1729.13],
RA3: [864.57, 1218.9],
RA4: [609.45, 864.57],
SRA0: [2551.18, 3628.35],
SRA1: [1814.17, 2551.18],
SRA2: [1275.59, 1814.17],
SRA3: [907.09, 1275.59],
SRA4: [637.80, 907.09],
EXECUTIVE: [521.86, 756.00],
FOLIO: [612.00, 936.00],
LEGAL: [612.00, 1008.00],
LETTER: [612.00, 792.00],
TABLOID: [792.00, 1224.00]
SRA4: [637.8, 907.09],
EXECUTIVE: [521.86, 756.0],
FOLIO: [612.0, 936.0],
LEGAL: [612.0, 1008.0],
LETTER: [612.0, 792.0],
TABLOID: [792.0, 1224.0]
};
class PDFPage {
constructor(document, options) {
this.document = document;
if (options == null) { options = {}; }
if (options == null) {
options = {};
}
this.size = options.size || 'letter';
this.layout = options.layout || 'portrait';
// process margins
if (typeof options.margin === 'number') {
this.margins = {
this.margins = {
top: options.margin,
left: options.margin,
bottom: options.margin,
right: options.margin
};
// default to 1 inch margins
// default to 1 inch margins
} else {
this.margins = options.margins || DEFAULT_MARGINS;
}
// calculate page dimensions
const dimensions = Array.isArray(this.size) ? this.size : SIZES[this.size.toUpperCase()];
const dimensions = Array.isArray(this.size)
? this.size
: SIZES[this.size.toUpperCase()];
this.width = dimensions[this.layout === 'portrait' ? 0 : 1];
this.height = dimensions[this.layout === 'portrait' ? 1 : 0];
this.content = this.document.ref();
// Initialize the Font, XObject, and ExtGState dictionaries
this.resources = this.document.ref({
ProcSet: ['PDF', 'Text', 'ImageB', 'ImageC', 'ImageI']});
ProcSet: ['PDF', 'Text', 'ImageB', 'ImageC', 'ImageI']
});
// The page dictionary
this.dictionary = this.document.ref({
Type: 'Page',
@ -130,11 +135,11 @@ class PDFPage {
const data = this.dictionary.data;
return data.Annots != null ? data.Annots : (data.Annots = []);
}
maxY() {
return this.height - this.margins.bottom;
}
write(chunk) {
return this.content.write(chunk);
}
@ -144,6 +149,6 @@ class PDFPage {
this.resources.end();
return this.content.end();
}
};
export default PDFPage;
}
export default PDFPage;

View File

@ -29,65 +29,83 @@ const parse = function(path) {
let cmd;
const ret = [];
let args = [];
let curArg = "";
let curArg = '';
let foundDecimal = false;
let params = 0;
for (let c of path) {
if (parameters[c] != null) {
params = parameters[c];
if (cmd) { // save existing command
if (curArg.length > 0) { args[args.length] = +curArg; }
ret[ret.length] = {cmd,args};
if (cmd) {
// save existing command
if (curArg.length > 0) {
args[args.length] = +curArg;
}
ret[ret.length] = { cmd, args };
args = [];
curArg = "";
curArg = '';
foundDecimal = false;
}
cmd = c;
} else if (
[' ', ','].includes(c) ||
(c === '-' && curArg.length > 0 && curArg[curArg.length - 1] !== 'e') ||
(c === '.' && foundDecimal)
) {
if (curArg.length === 0) {
continue;
}
} else if ([" ", ","].includes(c) || ((c === "-") && (curArg.length > 0) && (curArg[curArg.length - 1] !== 'e')) || ((c === ".") && foundDecimal)) {
if (curArg.length === 0) { continue; }
if (args.length === params) { // handle reused commands
ret[ret.length] = {cmd,args};
if (args.length === params) {
// handle reused commands
ret[ret.length] = { cmd, args };
args = [+curArg];
// handle assumed commands
if (cmd === "M") { cmd = "L"; }
if (cmd === "m") { cmd = "l"; }
if (cmd === 'M') {
cmd = 'L';
}
if (cmd === 'm') {
cmd = 'l';
}
} else {
args[args.length] = +curArg;
}
foundDecimal = (c === ".");
foundDecimal = c === '.';
// fix for negative numbers or repeated decimals with no delimeter between commands
curArg = ['-', '.'].includes(c) ? c : '';
} else {
curArg += c;
if (c === '.') { foundDecimal = true; }
if (c === '.') {
foundDecimal = true;
}
}
}
// add the last command
if (curArg.length > 0) {
if (args.length === params) { // handle reused commands
ret[ret.length] = {cmd, args};
if (args.length === params) {
// handle reused commands
ret[ret.length] = { cmd, args };
args = [+curArg];
// handle assumed commands
if (cmd === "M") { cmd = "L"; }
if (cmd === "m") { cmd = "l"; }
if (cmd === 'M') {
cmd = 'L';
}
if (cmd === 'm') {
cmd = 'l';
}
} else {
args[args.length] = +curArg;
}
}
ret[ret.length] = {cmd,args};
ret[ret.length] = { cmd, args };
return ret;
};
@ -102,14 +120,14 @@ const apply = function(commands, doc) {
if (typeof runners[c.cmd] === 'function') {
runners[c.cmd](doc, c.args);
}
}
}
};
const runners = {
const runners = {
M(doc, a) {
cx = a[0];
cy = a[1];
px = (py = null);
px = py = null;
sx = cx;
sy = cy;
return doc.moveTo(cx, cy);
@ -118,7 +136,7 @@ const runners = {
m(doc, a) {
cx += a[0];
cy += a[1];
px = (py = null);
px = py = null;
sx = cx;
sy = cy;
return doc.moveTo(cx, cy);
@ -133,11 +151,18 @@ const runners = {
},
c(doc, a) {
doc.bezierCurveTo(a[0] + cx, a[1] + cy, a[2] + cx, a[3] + cy, a[4] + cx, a[5] + cy);
doc.bezierCurveTo(
a[0] + cx,
a[1] + cy,
a[2] + cx,
a[3] + cy,
a[4] + cx,
a[5] + cy
);
px = cx + a[2];
py = cy + a[3];
cx += a[4];
return cy += a[5];
return (cy += a[5]);
},
S(doc, a) {
@ -146,11 +171,11 @@ const runners = {
py = cy;
}
doc.bezierCurveTo(cx-(px-cx), cy-(py-cy), a[0], a[1], a[2], a[3]);
doc.bezierCurveTo(cx - (px - cx), cy - (py - cy), a[0], a[1], a[2], a[3]);
px = a[0];
py = a[1];
cx = a[2];
return cy = a[3];
return (cy = a[3]);
},
s(doc, a) {
@ -158,14 +183,21 @@ const runners = {
px = cx;
py = cy;
}
doc.bezierCurveTo(cx-(px-cx), cy-(py-cy), cx + a[0], cy + a[1], cx + a[2], cy + a[3]);
doc.bezierCurveTo(
cx - (px - cx),
cy - (py - cy),
cx + a[0],
cy + a[1],
cx + a[2],
cy + a[3]
);
px = cx + a[0];
py = cy + a[1];
cx += a[2];
return cy += a[3];
return (cy += a[3]);
},
Q(doc, a) {
px = a[0];
py = a[1];
@ -179,23 +211,23 @@ const runners = {
px = cx + a[0];
py = cy + a[1];
cx += a[2];
return cy += a[3];
return (cy += a[3]);
},
T(doc, a) {
if (px === null) {
px = cx;
py = cy;
} else {
px = cx-(px-cx);
py = cy-(py-cy);
} else {
px = cx - (px - cx);
py = cy - (py - cy);
}
doc.quadraticCurveTo(px, py, a[0], a[1]);
px = cx-(px-cx);
py = cy-(py-cy);
px = cx - (px - cx);
py = cy - (py - cy);
cx = a[0];
return cy = a[1];
return (cy = a[1]);
},
t(doc, a) {
@ -203,19 +235,19 @@ const runners = {
px = cx;
py = cy;
} else {
px = cx-(px-cx);
py = cy-(py-cy);
px = cx - (px - cx);
py = cy - (py - cy);
}
doc.quadraticCurveTo(px, py, cx + a[0], cy + a[1]);
cx += a[0];
return cy += a[1];
return (cy += a[1]);
},
A(doc, a) {
solveArc(doc, cx, cy, a);
cx = a[5];
return cy = a[6];
return (cy = a[6]);
},
a(doc, a) {
@ -223,80 +255,80 @@ const runners = {
a[6] += cy;
solveArc(doc, cx, cy, a);
cx = a[5];
return cy = a[6];
return (cy = a[6]);
},
L(doc, a) {
cx = a[0];
cy = a[1];
px = (py = null);
px = py = null;
return doc.lineTo(cx, cy);
},
l(doc, a) {
cx += a[0];
cy += a[1];
px = (py = null);
px = py = null;
return doc.lineTo(cx, cy);
},
H(doc, a) {
cx = a[0];
px = (py = null);
px = py = null;
return doc.lineTo(cx, cy);
},
h(doc, a) {
cx += a[0];
px = (py = null);
px = py = null;
return doc.lineTo(cx, cy);
},
V(doc, a) {
cy = a[0];
px = (py = null);
px = py = null;
return doc.lineTo(cx, cy);
},
v(doc, a) {
cy += a[0];
px = (py = null);
px = py = null;
return doc.lineTo(cx, cy);
},
Z(doc) {
doc.closePath();
cx = sx;
return cy = sy;
return (cy = sy);
},
z(doc) {
doc.closePath();
cx = sx;
return cy = sy;
return (cy = sy);
}
};
const solveArc = function(doc, x, y, coords) {
const [rx,ry,rot,large,sweep,ex,ey] = coords;
const [rx, ry, rot, large, sweep, ex, ey] = coords;
const segs = arcToSegments(ex, ey, rx, ry, large, sweep, rot, x, y);
for (let seg of segs) {
const bez = segmentToBezier(...(seg || []));
doc.bezierCurveTo(...(bez || []));
}
}
};
// from Inkscape svgtopdf, thanks!
const arcToSegments = function(x, y, rx, ry, large, sweep, rotateX, ox, oy) {
const th = rotateX * (Math.PI/180);
const th = rotateX * (Math.PI / 180);
const sin_th = Math.sin(th);
const cos_th = Math.cos(th);
rx = Math.abs(rx);
ry = Math.abs(ry);
px = (cos_th * (ox - x) * 0.5) + (sin_th * (oy - y) * 0.5);
py = (cos_th * (oy - y) * 0.5) - (sin_th * (ox - x) * 0.5);
let pl = ((px*px) / (rx*rx)) + ((py*py) / (ry*ry));
px = cos_th * (ox - x) * 0.5 + sin_th * (oy - y) * 0.5;
py = cos_th * (oy - y) * 0.5 - sin_th * (ox - x) * 0.5;
let pl = (px * px) / (rx * rx) + (py * py) / (ry * ry);
if (pl > 1) {
pl = Math.sqrt(pl);
rx *= pl;
@ -305,38 +337,46 @@ const arcToSegments = function(x, y, rx, ry, large, sweep, rotateX, ox, oy) {
const a00 = cos_th / rx;
const a01 = sin_th / rx;
const a10 = (-sin_th) / ry;
const a11 = (cos_th) / ry;
const x0 = (a00 * ox) + (a01 * oy);
const y0 = (a10 * ox) + (a11 * oy);
const x1 = (a00 * x) + (a01 * y);
const y1 = (a10 * x) + (a11 * y);
const a10 = -sin_th / ry;
const a11 = cos_th / ry;
const x0 = a00 * ox + a01 * oy;
const y0 = a10 * ox + a11 * oy;
const x1 = a00 * x + a01 * y;
const y1 = a10 * x + a11 * y;
const d = ((x1-x0) * (x1-x0)) + ((y1-y0) * (y1-y0));
let sfactor_sq = (1 / d) - 0.25;
if (sfactor_sq < 0) { sfactor_sq = 0; }
const d = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0);
let sfactor_sq = 1 / d - 0.25;
if (sfactor_sq < 0) {
sfactor_sq = 0;
}
let sfactor = Math.sqrt(sfactor_sq);
if (sweep === large) { sfactor = -sfactor; }
if (sweep === large) {
sfactor = -sfactor;
}
const xc = (0.5 * (x0 + x1)) - (sfactor * (y1-y0));
const yc = (0.5 * (y0 + y1)) + (sfactor * (x1-x0));
const xc = 0.5 * (x0 + x1) - sfactor * (y1 - y0);
const yc = 0.5 * (y0 + y1) + sfactor * (x1 - x0);
const th0 = Math.atan2(y0-yc, x0-xc);
const th1 = Math.atan2(y1-yc, x1-xc);
const th0 = Math.atan2(y0 - yc, x0 - xc);
const th1 = Math.atan2(y1 - yc, x1 - xc);
let th_arc = th1-th0;
if ((th_arc < 0) && (sweep === 1)) {
th_arc += 2*Math.PI;
} else if ((th_arc > 0) && (sweep === 0)) {
let th_arc = th1 - th0;
if (th_arc < 0 && sweep === 1) {
th_arc += 2 * Math.PI;
} else if (th_arc > 0 && sweep === 0) {
th_arc -= 2 * Math.PI;
}
const segments = Math.ceil(Math.abs(th_arc / ((Math.PI * 0.5) + 0.001)));
const segments = Math.ceil(Math.abs(th_arc / (Math.PI * 0.5 + 0.001)));
const result = [];
for (let i = 0, end = segments, asc = 0 <= end; asc ? i < end : i > end; asc ? i++ : i--) {
const th2 = th0 + ((i * th_arc) / segments);
const th3 = th0 + (((i+1) * th_arc) / segments);
for (
let i = 0, end = segments, asc = 0 <= end;
asc ? i < end : i > end;
asc ? i++ : i--
) {
const th2 = th0 + (i * th_arc) / segments;
const th3 = th0 + ((i + 1) * th_arc) / segments;
result[i] = [xc, yc, th2, th3, rx, ry, sin_th, cos_th];
}
@ -350,18 +390,23 @@ const segmentToBezier = function(cx, cy, th0, th1, rx, ry, sin_th, cos_th) {
const a11 = cos_th * ry;
const th_half = 0.5 * (th1 - th0);
const t = ((8 / 3) * Math.sin(th_half * 0.5) * Math.sin(th_half * 0.5)) / Math.sin(th_half);
const x1 = (cx + Math.cos(th0)) - (t * Math.sin(th0));
const y1 = cy + Math.sin(th0) + (t * Math.cos(th0));
const t =
((8 / 3) * Math.sin(th_half * 0.5) * Math.sin(th_half * 0.5)) /
Math.sin(th_half);
const x1 = cx + Math.cos(th0) - t * Math.sin(th0);
const y1 = cy + Math.sin(th0) + t * Math.cos(th0);
const x3 = cx + Math.cos(th1);
const y3 = cy + Math.sin(th1);
const x2 = x3 + (t * Math.sin(th1));
const y2 = y3 - (t * Math.cos(th1));
const x2 = x3 + t * Math.sin(th1);
const y2 = y3 - t * Math.cos(th1);
return [
(a00 * x1) + (a01 * y1), (a10 * x1) + (a11 * y1),
(a00 * x2) + (a01 * y2), (a10 * x2) + (a11 * y2),
(a00 * x3) + (a01 * y3), (a10 * x3) + (a11 * y3)
a00 * x1 + a01 * y1,
a10 * x1 + a11 * y1,
a00 * x2 + a01 * y2,
a10 * x2 + a11 * y2,
a00 * x3 + a01 * y3,
a10 * x3 + a11 * y3
];
};
@ -370,6 +415,6 @@ class SVGPath {
const commands = parse(path);
apply(commands, doc);
}
};
}
export default SVGPath;

View File

@ -12,7 +12,9 @@ class PDFReference extends PDFAbstractReference {
super();
this.document = document;
this.id = id;
if (data == null) { data = {}; }
if (data == null) {
data = {};
}
this.data = data;
this.gen = 0;
this.compress = this.document.compress && !this.data.Filter;
@ -26,11 +28,13 @@ class PDFReference extends PDFAbstractReference {
}
this.uncompressedLength += chunk.length;
if (this.data.Length == null) { this.data.Length = 0; }
if (this.data.Length == null) {
this.data.Length = 0;
}
this.buffer.push(chunk);
this.data.Length += chunk.length;
if (this.compress) {
return this.data.Filter = 'FlateDecode';
return (this.data.Filter = 'FlateDecode');
}
}
@ -44,7 +48,9 @@ class PDFReference extends PDFAbstractReference {
finalize() {
this.offset = this.document._offset;
const encryptFn = this.document._security ? this.document._security.getEncryptFn(this.id, this.gen) : null;
const encryptFn = this.document._security
? this.document._security.getEncryptFn(this.id, this.gen)
: null;
if (this.buffer.length) {
this.buffer = Buffer.concat(this.buffer);

View File

@ -97,17 +97,32 @@ class PDFSecurity {
}
const paddedUserPassword = processPasswordR2R3R4(options.userPassword);
const paddedOwnerPassword = options.ownerPassword ?
processPasswordR2R3R4(options.ownerPassword) : paddedUserPassword;
const paddedOwnerPassword = options.ownerPassword
? processPasswordR2R3R4(options.ownerPassword)
: paddedUserPassword;
const ownerPasswordEntry = getOwnerPasswordR2R3R4(r, this.keyBits, paddedUserPassword, paddedOwnerPassword);
this.encryptionKey = getEncryptionKeyR2R3R4(r, this.keyBits, this.document._id,
paddedUserPassword, ownerPasswordEntry, permissions);
const ownerPasswordEntry = getOwnerPasswordR2R3R4(
r,
this.keyBits,
paddedUserPassword,
paddedOwnerPassword
);
this.encryptionKey = getEncryptionKeyR2R3R4(
r,
this.keyBits,
this.document._id,
paddedUserPassword,
ownerPasswordEntry,
permissions
);
let userPasswordEntry;
if (r === 2) {
userPasswordEntry = getUserPasswordR2(this.encryptionKey);
} else {
userPasswordEntry = getUserPasswordR3R4(this.document._id, this.encryptionKey);
userPasswordEntry = getUserPasswordR3R4(
this.document._id,
this.encryptionKey
);
}
encDict.V = v;
@ -136,19 +151,46 @@ class PDFSecurity {
const permissions = getPermissionsR3(options);
const processedUserPassword = processPasswordR5(options.userPassword);
const processedOwnerPassword = options.ownerPassword ?
processPasswordR5(options.ownerPassword) : processedUserPassword;
const processedOwnerPassword = options.ownerPassword
? processPasswordR5(options.ownerPassword)
: processedUserPassword;
this.encryptionKey = getEncryptionKeyR5(PDFSecurity.generateRandomWordArray);
const userPasswordEntry = getUserPasswordR5(processedUserPassword, PDFSecurity.generateRandomWordArray);
const userKeySalt = CryptoJS.lib.WordArray.create(userPasswordEntry.words.slice(10, 12), 8);
const userEncryptionKeyEntry = getUserEncryptionKeyR5(processedUserPassword, userKeySalt, this.encryptionKey);
const ownerPasswordEntry = getOwnerPasswordR5(processedOwnerPassword, userPasswordEntry,
PDFSecurity.generateRandomWordArray);
const ownerKeySalt = CryptoJS.lib.WordArray.create(ownerPasswordEntry.words.slice(10, 12), 8);
const ownerEncryptionKeyEntry = getOwnerEncryptionKeyR5(processedOwnerPassword, ownerKeySalt, userPasswordEntry,
this.encryptionKey);
const permsEntry = getEncryptedPermissionsR5(permissions, this.encryptionKey, PDFSecurity.generateRandomWordArray);
this.encryptionKey = getEncryptionKeyR5(
PDFSecurity.generateRandomWordArray
);
const userPasswordEntry = getUserPasswordR5(
processedUserPassword,
PDFSecurity.generateRandomWordArray
);
const userKeySalt = CryptoJS.lib.WordArray.create(
userPasswordEntry.words.slice(10, 12),
8
);
const userEncryptionKeyEntry = getUserEncryptionKeyR5(
processedUserPassword,
userKeySalt,
this.encryptionKey
);
const ownerPasswordEntry = getOwnerPasswordR5(
processedOwnerPassword,
userPasswordEntry,
PDFSecurity.generateRandomWordArray
);
const ownerKeySalt = CryptoJS.lib.WordArray.create(
ownerPasswordEntry.words.slice(10, 12),
8
);
const ownerEncryptionKeyEntry = getOwnerEncryptionKeyR5(
processedOwnerPassword,
ownerKeySalt,
userPasswordEntry,
this.encryptionKey
);
const permsEntry = getEncryptedPermissionsR5(
permissions,
this.encryptionKey,
PDFSecurity.generateRandomWordArray
);
encDict.V = 5;
encDict.Length = this.keyBits;
@ -173,21 +215,37 @@ class PDFSecurity {
getEncryptFn(obj, gen) {
let digest;
if (this.version < 5) {
digest = this.encryptionKey.clone().concat(CryptoJS.lib.WordArray.create([
((obj & 0xff) << 24) | ((obj & 0xff00) << 8) | ((obj >> 8) & 0xff00) | (gen & 0xff), (gen & 0xff00) << 16
], 5));
digest = this.encryptionKey
.clone()
.concat(
CryptoJS.lib.WordArray.create(
[
((obj & 0xff) << 24) |
((obj & 0xff00) << 8) |
((obj >> 8) & 0xff00) |
(gen & 0xff),
(gen & 0xff00) << 16
],
5
)
);
}
if (this.version === 1 || this.version === 2) {
let key = CryptoJS.MD5(digest);
key.sigBytes = Math.min(16, this.keyBits / 8 + 5);
return buffer => wordArrayToBuffer(
CryptoJS.RC4.encrypt(CryptoJS.lib.WordArray.create(buffer), key).ciphertext);
return buffer =>
wordArrayToBuffer(
CryptoJS.RC4.encrypt(CryptoJS.lib.WordArray.create(buffer), key)
.ciphertext
);
}
let key;
if (this.version === 4) {
key = CryptoJS.MD5(digest.concat(CryptoJS.lib.WordArray.create([0x73416c54], 4)));
key = CryptoJS.MD5(
digest.concat(CryptoJS.lib.WordArray.create([0x73416c54], 4))
);
} else {
key = this.encryptionKey;
}
@ -199,8 +257,18 @@ class PDFSecurity {
iv
};
return buffer => wordArrayToBuffer(
iv.clone().concat(CryptoJS.AES.encrypt(CryptoJS.lib.WordArray.create(buffer), key, options).ciphertext));
return buffer =>
wordArrayToBuffer(
iv
.clone()
.concat(
CryptoJS.AES.encrypt(
CryptoJS.lib.WordArray.create(buffer),
key,
options
).ciphertext
)
);
}
end() {
@ -255,23 +323,32 @@ function getPermissionsR3(permissionObject = {}) {
}
function getUserPasswordR2(encryptionKey) {
return CryptoJS.RC4.encrypt(processPasswordR2R3R4(), encryptionKey).ciphertext;
return CryptoJS.RC4.encrypt(processPasswordR2R3R4(), encryptionKey)
.ciphertext;
}
function getUserPasswordR3R4(documentId, encryptionKey) {
const key = encryptionKey.clone();
let cipher = CryptoJS.MD5(processPasswordR2R3R4().concat(CryptoJS.lib.WordArray.create(documentId)));
let cipher = CryptoJS.MD5(
processPasswordR2R3R4().concat(CryptoJS.lib.WordArray.create(documentId))
);
for (let i = 0; i < 20; i++) {
const xorRound = Math.ceil(key.sigBytes / 4);
for (let j = 0; j < xorRound; j++) {
key.words[j] = encryptionKey.words[j] ^ (i | (i << 8) | (i << 16) | (i << 24));
key.words[j] =
encryptionKey.words[j] ^ (i | (i << 8) | (i << 16) | (i << 24));
}
cipher = CryptoJS.RC4.encrypt(cipher, key).ciphertext;
}
return cipher.concat(CryptoJS.lib.WordArray.create(null, 16));
}
function getOwnerPasswordR2R3R4(r, keyBits, paddedUserPassword, paddedOwnerPassword) {
function getOwnerPasswordR2R3R4(
r,
keyBits,
paddedUserPassword,
paddedOwnerPassword
) {
let digest = paddedOwnerPassword;
let round = r >= 3 ? 51 : 1;
for (let i = 0; i < round; i++) {
@ -292,8 +369,16 @@ function getOwnerPasswordR2R3R4(r, keyBits, paddedUserPassword, paddedOwnerPassw
return cipher;
}
function getEncryptionKeyR2R3R4(r, keyBits, documentId, paddedUserPassword, ownerPasswordEntry, permissions) {
let key = paddedUserPassword.clone()
function getEncryptionKeyR2R3R4(
r,
keyBits,
documentId,
paddedUserPassword,
ownerPasswordEntry,
permissions
) {
let key = paddedUserPassword
.clone()
.concat(ownerPasswordEntry)
.concat(CryptoJS.lib.WordArray.create([lsbFirstWord(permissions)], 4))
.concat(CryptoJS.lib.WordArray.create(documentId));
@ -309,11 +394,18 @@ function getUserPasswordR5(processedUserPassword, generateRandomWordArray) {
const validationSalt = generateRandomWordArray(8);
const keySalt = generateRandomWordArray(8);
return CryptoJS.SHA256(processedUserPassword.clone().concat(validationSalt))
.concat(validationSalt).concat(keySalt);
.concat(validationSalt)
.concat(keySalt);
}
function getUserEncryptionKeyR5(processedUserPassword, userKeySalt, encryptionKey) {
const key = CryptoJS.SHA256(processedUserPassword.clone().concat(userKeySalt));
function getUserEncryptionKeyR5(
processedUserPassword,
userKeySalt,
encryptionKey
) {
const key = CryptoJS.SHA256(
processedUserPassword.clone().concat(userKeySalt)
);
const options = {
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.NoPadding,
@ -322,15 +414,35 @@ function getUserEncryptionKeyR5(processedUserPassword, userKeySalt, encryptionKe
return CryptoJS.AES.encrypt(encryptionKey, key, options).ciphertext;
}
function getOwnerPasswordR5(processedOwnerPassword, userPasswordEntry, generateRandomWordArray) {
function getOwnerPasswordR5(
processedOwnerPassword,
userPasswordEntry,
generateRandomWordArray
) {
const validationSalt = generateRandomWordArray(8);
const keySalt = generateRandomWordArray(8);
return CryptoJS.SHA256(processedOwnerPassword.clone().concat(validationSalt).concat(userPasswordEntry))
.concat(validationSalt).concat(keySalt);
return CryptoJS.SHA256(
processedOwnerPassword
.clone()
.concat(validationSalt)
.concat(userPasswordEntry)
)
.concat(validationSalt)
.concat(keySalt);
}
function getOwnerEncryptionKeyR5(processedOwnerPassword, ownerKeySalt, userPasswordEntry, encryptionKey) {
const key = CryptoJS.SHA256(processedOwnerPassword.clone().concat(ownerKeySalt).concat(userPasswordEntry));
function getOwnerEncryptionKeyR5(
processedOwnerPassword,
ownerKeySalt,
userPasswordEntry,
encryptionKey
) {
const key = CryptoJS.SHA256(
processedOwnerPassword
.clone()
.concat(ownerKeySalt)
.concat(userPasswordEntry)
);
const options = {
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.NoPadding,
@ -343,9 +455,15 @@ function getEncryptionKeyR5(generateRandomWordArray) {
return generateRandomWordArray(32);
}
function getEncryptedPermissionsR5(permissions, encryptionKey, generateRandomWordArray) {
const cipher = CryptoJS.lib.WordArray.create([lsbFirstWord(permissions), 0xffffffff, 0x54616462], 12)
.concat(generateRandomWordArray(4));
function getEncryptedPermissionsR5(
permissions,
encryptionKey,
generateRandomWordArray
) {
const cipher = CryptoJS.lib.WordArray.create(
[lsbFirstWord(permissions), 0xffffffff, 0x54616462],
12
).concat(generateRandomWordArray(4));
const options = {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.NoPadding
@ -385,20 +503,57 @@ function processPasswordR5(password = '') {
}
function lsbFirstWord(data) {
return ((data & 0xff) << 24) | ((data & 0xff00) << 8) | ((data >> 8) & 0xff00) | ((data >> 24) & 0xff);
return (
((data & 0xff) << 24) |
((data & 0xff00) << 8) |
((data >> 8) & 0xff00) |
((data >> 24) & 0xff)
);
}
function wordArrayToBuffer(wordArray) {
const byteArray = [];
for (let i = 0; i < wordArray.sigBytes; i++) {
byteArray.push((wordArray.words[Math.floor(i / 4)] >> (8 * (3 - i % 4))) & 0xff);
byteArray.push(
(wordArray.words[Math.floor(i / 4)] >> (8 * (3 - (i % 4)))) & 0xff
);
}
return Buffer.from(byteArray);
}
const PASSWORD_PADDING = [
0x28, 0xbf, 0x4e, 0x5e, 0x4e, 0x75, 0x8a, 0x41, 0x64, 0x00, 0x4e, 0x56, 0xff, 0xfa, 0x01, 0x08,
0x2e, 0x2e, 0x00, 0xb6, 0xd0, 0x68, 0x3e, 0x80, 0x2f, 0x0c, 0xa9, 0xfe, 0x64, 0x53, 0x69, 0x7a
0x28,
0xbf,
0x4e,
0x5e,
0x4e,
0x75,
0x8a,
0x41,
0x64,
0x00,
0x4e,
0x56,
0xff,
0xfa,
0x01,
0x08,
0x2e,
0x2e,
0x00,
0xb6,
0xd0,
0x68,
0x3e,
0x80,
0x2f,
0x0c,
0xa9,
0xfe,
0x64,
0x53,
0x69,
0x7a
];
export default PDFSecurity;

View File

@ -36,6 +36,7 @@
"jade": "~1.1.5",
"jest": "^23.4.2",
"markdown": "~0.5.0",
"prettier": "1.15.3",
"rollup": "^0.65.0",
"rollup-plugin-babel": "^3.0.7",
"rollup-plugin-cpy": "^1.0.0"
@ -55,6 +56,7 @@
"pdf-guide": "node docs/generate.js",
"website": "node docs/generate_website.js",
"docs": "npm run pdf-guide && npm run website && npm run browser-demo",
"prettier": "prettier {lib,tests,demo,docs}/**/*.js",
"test": "jest -i",
"test:integration": "jest integration/ -i",
"test:unit": "jest unit/ -i"

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
var {runDocTest} = require('./helpers');
var { runDocTest } = require('./helpers');
const characters = `Latin
ABCDEFGHIJKLMNOPQRSTUVWXYZ
@ -22,33 +22,33 @@ Cyrillic 1 (Russian)
Cyrillic 2 (Extended)
ЀЁЂЃЄЅІЇЈЉЊЋЌЍЎЏҐӁƏҒҖҚҢҮҰҲҶҺӘӢӨӮ
ѐёђѓєѕіїјљњћќѝўџґӂǝғҗқңүұҳҷһәӣөӯ`
ѐёђѓєѕіїјљњћќѝўџґӂǝғҗқңүұҳҷһәӣөӯ`;
describe('fonts', function () {
test('default (Helvetica)', function () {
describe('fonts', function() {
test('default (Helvetica)', function() {
return runDocTest(function(doc) {
doc.text(characters, 10, 10);
});
});
test('Helvetica Bold', function () {
test('Helvetica Bold', function() {
return runDocTest(function(doc) {
doc.font('Helvetica-Bold');
doc.text(characters, 10, 10);
doc.text(characters, 10, 10);
});
});
test('Roboto', function () {
test('Roboto', function() {
return runDocTest(function(doc) {
doc.font('tests/fonts/Roboto-Regular.ttf');
doc.text(characters, 10, 10);
doc.text(characters, 10, 10);
});
});
test('Roboto Bold', function () {
test('Roboto Bold', function() {
return runDocTest(function(doc) {
doc.font('tests/fonts/Roboto-Medium.ttf');
doc.text(characters, 10, 10);
doc.font('tests/fonts/Roboto-Medium.ttf');
doc.text(characters, 10, 10);
});
});
});
});

View File

@ -7,17 +7,17 @@ var fs = require('fs');
// manual mock for PDFSecurity to ensure stored id will be the same accross different systems
PDFSecurity.generateFileID = () => {
return new Buffer('mocked-pdf-id');
}
};
PDFSecurity.generateRandomWordArray = (bytes) => {
PDFSecurity.generateRandomWordArray = bytes => {
const words = [];
for (let i = 0; i < bytes; i++) {
words.push(0x00010203);
}
return new CryptoJS.lib.WordArray.init(words, bytes);
}
};
function updatePdf (pdfData, testState, snapshotChanges) {
function updatePdf(pdfData, testState, snapshotChanges) {
const pdfDir = path.join(path.dirname(testState.testPath), '__pdfs__');
if (!fs.existsSync(pdfDir)) {
fs.mkdirSync(pdfDir);
@ -26,7 +26,7 @@ function updatePdf (pdfData, testState, snapshotChanges) {
const fileRefPath = path.join(pdfDir, testState.currentTestName + '.pdf');
const fileChangesPath = fileRefPath.replace('.pdf', '[changed].pdf');
const {matched, added, unmatched, updated} = snapshotChanges;
const { matched, added, unmatched, updated } = snapshotChanges;
if (added || updated || (matched && !fs.existsSync(fileRefPath))) {
fs.writeFileSync(fileRefPath, pdfData);
@ -43,17 +43,16 @@ function updatePdf (pdfData, testState, snapshotChanges) {
}
}
function getSnapshotChanges(snapshotState) {
const {matched, added, unmatched, updated} = snapshotState
return {matched, added, unmatched, updated}
const { matched, added, unmatched, updated } = snapshotState;
return { matched, added, unmatched, updated };
}
function compareSnapshotChanges(changes, previousChanges) {
return Object.keys(changes).reduce((result, key) => {
result[key] = changes[key] - previousChanges[key]
return result
}, {})
result[key] = changes[key] - previousChanges[key];
return result;
}, {});
}
function runDocTest(options, fn) {
@ -64,7 +63,7 @@ function runDocTest(options, fn) {
if (!options.info) {
options.info = {};
}
options.info.CreationDate = new Date(Date.UTC(2018,1,1));
options.info.CreationDate = new Date(Date.UTC(2018, 1, 1));
return new Promise(function(resolve) {
var doc = new PDFDocument(options);
@ -72,20 +71,24 @@ function runDocTest(options, fn) {
fn(doc);
doc.on('data', buffers.push.bind(buffers))
doc.on('data', buffers.push.bind(buffers));
doc.on('end', () => {
const testState = expect.getState()
const pdfData = Buffer.concat(buffers)
const previousChanges = getSnapshotChanges(testState.snapshotState)
expect(pdfData.toString()).toMatchSnapshot()
const changes = getSnapshotChanges(testState.snapshotState)
updatePdf(pdfData, testState, compareSnapshotChanges(changes, previousChanges))
const testState = expect.getState();
const pdfData = Buffer.concat(buffers);
const previousChanges = getSnapshotChanges(testState.snapshotState);
expect(pdfData.toString()).toMatchSnapshot();
const changes = getSnapshotChanges(testState.snapshotState);
updatePdf(
pdfData,
testState,
compareSnapshotChanges(changes, previousChanges)
);
resolve();
});
doc.end();
})
});
}
module.exports = {
runDocTest: runDocTest
}
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,170 +1,307 @@
var {runDocTest} = require('../helpers');
var { runDocTest } = require('../helpers');
describe('pdfmake', function () {
test('basics', function () {
describe('pdfmake', function() {
test('basics', function() {
return runDocTest(function(doc) {
doc.fill('black');
doc.fillColor('black');
doc.fillOpacity(undefined);
doc.fontSize(12);
doc.text('First ',40,40,{lineBreak: false, textWidth: 26.68359375, characterSpacing: 0, wordCount: 1, link: null});
doc.text('First ', 40, 40, {
lineBreak: false,
textWidth: 26.68359375,
characterSpacing: 0,
wordCount: 1,
link: null
});
doc.save();
doc.restore();
doc.fill('black');
doc.fillColor('black');
doc.fillOpacity(undefined);
doc.fontSize(12);
doc.text('paragraph',66.68359375,40,{lineBreak: false, textWidth: 54.041015625, characterSpacing: 0, wordCount: 1, link: null});
doc.text('paragraph', 66.68359375, 40, {
lineBreak: false,
textWidth: 54.041015625,
characterSpacing: 0,
wordCount: 1,
link: null
});
doc.save();
doc.restore();
doc.fill('black');
doc.fillColor('black');
doc.fillOpacity(undefined);
doc.fontSize(12);
doc.text('Another ',40,54.0625,{lineBreak: false, textWidth: 45.2109375, characterSpacing: 0, wordCount: 1, link: null});
doc.text('Another ', 40, 54.0625, {
lineBreak: false,
textWidth: 45.2109375,
characterSpacing: 0,
wordCount: 1,
link: null
});
doc.save();
doc.restore();
doc.fill('black');
doc.fillColor('black');
doc.fillOpacity(undefined);
doc.fontSize(12);
doc.text('paragraph, ',85.2109375,54.0625,{lineBreak: false, textWidth: 59.3671875, characterSpacing: 0, wordCount: 1, link: null});
doc.text('paragraph, ', 85.2109375, 54.0625, {
lineBreak: false,
textWidth: 59.3671875,
characterSpacing: 0,
wordCount: 1,
link: null
});
doc.save();
doc.restore();
doc.fill('black');
doc.fillColor('black');
doc.fillOpacity(undefined);
doc.fontSize(12);
doc.text('this ',144.578125,54.0625,{lineBreak: false, textWidth: 22.599609375, characterSpacing: 0, wordCount: 1, link: null});
doc.text('this ', 144.578125, 54.0625, {
lineBreak: false,
textWidth: 22.599609375,
characterSpacing: 0,
wordCount: 1,
link: null
});
doc.save();
doc.restore();
doc.fill('black');
doc.fillColor('black');
doc.fillOpacity(undefined);
doc.fontSize(12);
doc.text('time ',167.177734375,54.0625,{lineBreak: false, textWidth: 26.677734375, characterSpacing: 0, wordCount: 1, link: null});
doc.text('time ', 167.177734375, 54.0625, {
lineBreak: false,
textWidth: 26.677734375,
characterSpacing: 0,
wordCount: 1,
link: null
});
doc.save();
doc.restore();
doc.fill('black');
doc.fillColor('black');
doc.fillOpacity(undefined);
doc.fontSize(12);
doc.text('a ',193.85546875,54.0625,{lineBreak: false, textWidth: 9.498046875, characterSpacing: 0, wordCount: 1, link: null});
doc.text('a ', 193.85546875, 54.0625, {
lineBreak: false,
textWidth: 9.498046875,
characterSpacing: 0,
wordCount: 1,
link: null
});
doc.save();
doc.restore();
doc.fill('black');
doc.fillColor('black');
doc.fillOpacity(undefined);
doc.fontSize(12);
doc.text('little ',203.353515625,54.0625,{lineBreak: false, textWidth: 25.904296875, characterSpacing: 0, wordCount: 1, link: null});
doc.text('little ', 203.353515625, 54.0625, {
lineBreak: false,
textWidth: 25.904296875,
characterSpacing: 0,
wordCount: 1,
link: null
});
doc.save();
doc.restore();
doc.fill('black');
doc.fillColor('black');
doc.fillOpacity(undefined);
doc.fontSize(12);
doc.text('bit ',229.2578125,54.0625,{lineBreak: false, textWidth: 16.53515625, characterSpacing: 0, wordCount: 1, link: null});
doc.text('bit ', 229.2578125, 54.0625, {
lineBreak: false,
textWidth: 16.53515625,
characterSpacing: 0,
wordCount: 1,
link: null
});
doc.save();
doc.restore();
doc.fill('black');
doc.fillColor('black');
doc.fillOpacity(undefined);
doc.fontSize(12);
doc.text('longer ',245.79296875,54.0625,{lineBreak: false, textWidth: 36.498046875, characterSpacing: 0, wordCount: 1, link: null});
doc.text('longer ', 245.79296875, 54.0625, {
lineBreak: false,
textWidth: 36.498046875,
characterSpacing: 0,
wordCount: 1,
link: null
});
doc.save();
doc.restore();
doc.fill('black');
doc.fillColor('black');
doc.fillOpacity(undefined);
doc.fontSize(12);
doc.text('to ',282.291015625,54.0625,{lineBreak: false, textWidth: 13.6171875, characterSpacing: 0, wordCount: 1, link: null});
doc.text('to ', 282.291015625, 54.0625, {
lineBreak: false,
textWidth: 13.6171875,
characterSpacing: 0,
wordCount: 1,
link: null
});
doc.save();
doc.restore();
doc.fill('black');
doc.fillColor('black');
doc.fillOpacity(undefined);
doc.fontSize(12);
doc.text('make ',295.908203125,54.0625,{lineBreak: false, textWidth: 32.337890625, characterSpacing: 0, wordCount: 1, link: null});
doc.text('make ', 295.908203125, 54.0625, {
lineBreak: false,
textWidth: 32.337890625,
characterSpacing: 0,
wordCount: 1,
link: null
});
doc.save();
doc.restore();
doc.fill('black');
doc.fillColor('black');
doc.fillOpacity(undefined);
doc.fontSize(12);
doc.text('sure, ',328.24609375,54.0625,{lineBreak: false, textWidth: 28.435546875, characterSpacing: 0, wordCount: 1, link: null});
doc.text('sure, ', 328.24609375, 54.0625, {
lineBreak: false,
textWidth: 28.435546875,
characterSpacing: 0,
wordCount: 1,
link: null
});
doc.save();
doc.restore();
doc.fill('black');
doc.fillColor('black');
doc.fillOpacity(undefined);
doc.fontSize(12);
doc.text('this ',356.681640625,54.0625,{lineBreak: false, textWidth: 22.599609375, characterSpacing: 0, wordCount: 1, link: null});
doc.text('this ', 356.681640625, 54.0625, {
lineBreak: false,
textWidth: 22.599609375,
characterSpacing: 0,
wordCount: 1,
link: null
});
doc.save();
doc.restore();
doc.fill('black');
doc.fillColor('black');
doc.fillOpacity(undefined);
doc.fontSize(12);
doc.text('line ',379.28125,54.0625,{lineBreak: false, textWidth: 21.7734375, characterSpacing: 0, wordCount: 1, link: null});
doc.text('line ', 379.28125, 54.0625, {
lineBreak: false,
textWidth: 21.7734375,
characterSpacing: 0,
wordCount: 1,
link: null
});
doc.save();
doc.restore();
doc.fill('black');
doc.fillColor('black');
doc.fillOpacity(undefined);
doc.fontSize(12);
doc.text('will ',401.0546875,54.0625,{lineBreak: false, textWidth: 20.724609375, characterSpacing: 0, wordCount: 1, link: null});
doc.text('will ', 401.0546875, 54.0625, {
lineBreak: false,
textWidth: 20.724609375,
characterSpacing: 0,
wordCount: 1,
link: null
});
doc.save();
doc.restore();
doc.fill('black');
doc.fillColor('black');
doc.fillOpacity(undefined);
doc.fontSize(12);
doc.text('be ',421.779296875,54.0625,{lineBreak: false, textWidth: 16.060546875, characterSpacing: 0, wordCount: 1, link: null});
doc.text('be ', 421.779296875, 54.0625, {
lineBreak: false,
textWidth: 16.060546875,
characterSpacing: 0,
wordCount: 1,
link: null
});
doc.save();
doc.restore();
doc.fill('black');
doc.fillColor('black');
doc.fillOpacity(undefined);
doc.fontSize(12);
doc.text('divided ',437.83984375,54.0625,{lineBreak: false, textWidth: 41.267578125, characterSpacing: 0, wordCount: 1, link: null});
doc.text('divided ', 437.83984375, 54.0625, {
lineBreak: false,
textWidth: 41.267578125,
characterSpacing: 0,
wordCount: 1,
link: null
});
doc.save();
doc.restore();
doc.fill('black');
doc.fillColor('black');
doc.fillOpacity(undefined);
doc.fontSize(12);
doc.text('into ',479.107421875,54.0625,{lineBreak: false, textWidth: 23.150390625, characterSpacing: 0, wordCount: 1, link: null});
doc.text('into ', 479.107421875, 54.0625, {
lineBreak: false,
textWidth: 23.150390625,
characterSpacing: 0,
wordCount: 1,
link: null
});
doc.save();
doc.restore();
doc.fill('black');
doc.fillColor('black');
doc.fillOpacity(undefined);
doc.fontSize(12);
doc.text('at ',502.2578125,54.0625,{lineBreak: false, textWidth: 13.41796875, characterSpacing: 0, wordCount: 1, link: null});
doc.text('at ', 502.2578125, 54.0625, {
lineBreak: false,
textWidth: 13.41796875,
characterSpacing: 0,
wordCount: 1,
link: null
});
doc.save();
doc.restore();
doc.fill('black');
doc.fillColor('black');
doc.fillOpacity(undefined);
doc.fontSize(12);
doc.text('least ',515.67578125,54.0625,{lineBreak: false, textWidth: 28.875, characterSpacing: 0, wordCount: 1, link: null});
doc.text('least ', 515.67578125, 54.0625, {
lineBreak: false,
textWidth: 28.875,
characterSpacing: 0,
wordCount: 1,
link: null
});
doc.save();
doc.restore();
doc.fill('black');
doc.fillColor('black');
doc.fillOpacity(undefined);
doc.fontSize(12);
doc.text('two ',40,68.125,{lineBreak: false, textWidth: 22.751953125, characterSpacing: 0, wordCount: 1, link: null});
doc.text('two ', 40, 68.125, {
lineBreak: false,
textWidth: 22.751953125,
characterSpacing: 0,
wordCount: 1,
link: null
});
doc.save();
doc.restore();
doc.fill('black');
doc.fillColor('black');
doc.fillOpacity(undefined);
doc.fontSize(12);
doc.text('lines',62.751953125,68.125,{lineBreak: false, textWidth: 24.990234375, characterSpacing: 0, wordCount: 1, link: null});
doc.text('lines', 62.751953125, 68.125, {
lineBreak: false,
textWidth: 24.990234375,
characterSpacing: 0,
wordCount: 1,
link: null
});
doc.save();
doc.restore();
});
});
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,56 +1,79 @@
var {runDocTest} = require('./helpers');
var { runDocTest } = require('./helpers');
var PDFDocument = require('../../lib/document').default;
var CryptoJS = require('crypto-js');
describe('pdfmake', function () {
describe('pdfmake', function() {
let generateRandomWordArray = null;
beforeAll(function () {
beforeAll(function() {
const doc = new PDFDocument({ userPassword: 'user' });
generateRandomWordArray = Object.getPrototypeOf(doc._security).constructor.generateRandomWordArray;
Object.getPrototypeOf(doc._security).constructor.generateRandomWordArray = function (bytes) {
generateRandomWordArray = Object.getPrototypeOf(doc._security).constructor
.generateRandomWordArray;
Object.getPrototypeOf(
doc._security
).constructor.generateRandomWordArray = function(bytes) {
return CryptoJS.lib.WordArray.create(null, bytes);
};
});
afterAll(function () {
afterAll(function() {
const doc = new PDFDocument({ userPassword: 'user' });
Object.getPrototypeOf(doc._security).constructor.generateRandomWordArray = generateRandomWordArray;
Object.getPrototypeOf(
doc._security
).constructor.generateRandomWordArray = generateRandomWordArray;
});
test('encryption with RC-40 (PDF 1.3)', function () {
return runDocTest({ pdfVersion: '1.3', userPassword: 'user', ownerPassword: 'owner' }, function(doc) {
doc.text('test');
});
test('encryption with RC-40 (PDF 1.3)', function() {
return runDocTest(
{ pdfVersion: '1.3', userPassword: 'user', ownerPassword: 'owner' },
function(doc) {
doc.text('test');
}
);
});
test('encryption with RC-128 (PDF 1.4)', function () {
return runDocTest({ pdfVersion: '1.4', userPassword: 'user', ownerPassword: 'owner' }, function(doc) {
doc.text('test');
});
test('encryption with RC-128 (PDF 1.4)', function() {
return runDocTest(
{ pdfVersion: '1.4', userPassword: 'user', ownerPassword: 'owner' },
function(doc) {
doc.text('test');
}
);
});
test('encryption with RC-128 (PDF 1.5)', function () {
return runDocTest({ pdfVersion: '1.5', userPassword: 'user', ownerPassword: 'owner' }, function(doc) {
doc.text('test');
});
test('encryption with RC-128 (PDF 1.5)', function() {
return runDocTest(
{ pdfVersion: '1.5', userPassword: 'user', ownerPassword: 'owner' },
function(doc) {
doc.text('test');
}
);
});
test('encryption with AES-128 (PDF 1.6)', function () {
return runDocTest({ pdfVersion: '1.6', userPassword: 'user', ownerPassword: 'owner' }, function(doc) {
doc.text('test');
});
test('encryption with AES-128 (PDF 1.6)', function() {
return runDocTest(
{ pdfVersion: '1.6', userPassword: 'user', ownerPassword: 'owner' },
function(doc) {
doc.text('test');
}
);
});
test('encryption with AES-128 (PDF 1.7)', function () {
return runDocTest({ pdfVersion: '1.7', userPassword: 'user', ownerPassword: 'owner' }, function(doc) {
doc.text('test');
});
test('encryption with AES-128 (PDF 1.7)', function() {
return runDocTest(
{ pdfVersion: '1.7', userPassword: 'user', ownerPassword: 'owner' },
function(doc) {
doc.text('test');
}
);
});
test('encryption with AES-256 (PDF 1.7 extension 3)', function () {
return runDocTest({ pdfVersion: '1.7ext3', userPassword: 'user', ownerPassword: 'owner' }, function(doc) {
doc.text('test');
});
test('encryption with AES-256 (PDF 1.7 extension 3)', function() {
return runDocTest(
{ pdfVersion: '1.7ext3', userPassword: 'user', ownerPassword: 'owner' },
function(doc) {
doc.text('test');
}
);
});
});

View File

@ -1,23 +1,26 @@
var {runDocTest} = require('./helpers');
var { runDocTest } = require('./helpers');
describe('text', function () {
test('simple text', function () {
describe('text', function() {
test('simple text', function() {
return runDocTest(function(doc) {
doc.text('Really simple text', 100, 100);
});
});
test('alignment', function () {
test('alignment', function() {
return runDocTest(function(doc) {
doc.text('Left aligned text', {align: 'left'});
doc.text('Right aligned text', {align: 'right'});
doc.text('Justified aligned text - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam in suscipit purus.', {align: 'justify'});
doc.text('Left aligned text', { align: 'left' });
doc.text('Right aligned text', { align: 'right' });
doc.text(
'Justified aligned text - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam in suscipit purus.',
{ align: 'justify' }
);
});
});
test('decoration', function () {
test('decoration', function() {
return runDocTest(function(doc) {
doc.fillColor("blue").text('Here is a link!', 100, 100, {
doc.fillColor('blue').text('Here is a link!', 100, 100, {
link: 'http://google.com/',
underline: true
});
@ -25,11 +28,11 @@ describe('text', function () {
strike: true
});
});
});
});
test('list', function () {
test('list', function() {
return runDocTest(function(doc) {
doc.fillColor('#000').list(['One', 'Two', 'Three'], 100, 150);
});
});
});
});

View File

@ -1,30 +1,31 @@
var {runDocTest} = require('./helpers');
var tiger = require('../../demo/tiger')
var { runDocTest } = require('./helpers');
var tiger = require('../../demo/tiger');
describe('vector', function () {
test('simple shapes', function () {
describe('vector', function() {
test('simple shapes', function() {
return runDocTest(function(doc) {
doc.save()
.moveTo(100, 150)
.lineTo(100, 250)
.lineTo(200, 250)
.fill("#FF3300");
doc.circle(280, 200, 50)
.fill("#6600FF");
// an SVG path
doc.scale(0.6)
.translate(470, 130)
.path('M 250,75 L 323,301 131,161 369,161 177,301 z')
.fill('red', 'even-odd')
.restore();
doc
.save()
.moveTo(100, 150)
.lineTo(100, 250)
.lineTo(200, 250)
.fill('#FF3300');
doc.circle(280, 200, 50).fill('#6600FF');
// an SVG path
doc
.scale(0.6)
.translate(470, 130)
.path('M 250,75 L 323,301 131,161 369,161 177,301 z')
.fill('red', 'even-odd')
.restore();
});
});
test('complex svg', function () {
test('complex svg', function() {
return runDocTest(function(doc) {
var i, len, part
var i, len, part;
doc.translate(220, 300);
for (i = 0, len = tiger.length; i < len; i++) {
part = tiger[i];
@ -44,21 +45,21 @@ describe('vector', function () {
}
}
doc.restore();
}
}
});
});
test('svg path', function () {
test('svg path', function() {
return runDocTest(function(doc) {
// extracted from https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths
// lines
doc.path('M10 10 H 90 V 90 H 10 L 10 10');
doc.stroke('#000');
doc.translate(0, 100);
doc.path('M10 10 H 90 V 90 H 10 Z');
doc.stroke('#000');
doc.translate(0, 100);
doc.translate(0, 100);
doc.path('M10 10 h 80 v 80 h -80 Z');
doc.stroke('#000');
@ -76,7 +77,7 @@ describe('vector', function () {
doc.stroke('#000');
doc.path('M10 60 C 20 80, 40 80, 50 60');
doc.stroke('#000');
doc.stroke('#000');
doc.path('M70 60 C 70 80, 110 80, 110 60');
doc.stroke('#000');
@ -92,7 +93,7 @@ describe('vector', function () {
doc.path('M130 110 C 120 140, 180 140, 170 110');
doc.stroke('#000');
doc.translate(0, 120);
doc.path('M10 80 C 40 10, 65 10, 95 80 S 150 150, 180 80');
doc.stroke('#000');
@ -104,7 +105,7 @@ describe('vector', function () {
doc.translate(0, 120);
doc.path('M10 80 Q 52.5 10, 95 80 T 180 80');
doc.stroke('#000');
// arcs
doc.addPage();
doc.path(`M10 315
@ -113,8 +114,8 @@ describe('vector', function () {
L 172.55 152.45
A 30 50 -45 0 1 215.1 109.9
L 315 10`);
doc.fillAndStroke('#73B373', '#000');
doc.fillAndStroke('#73B373', '#000');
doc.translate(0, 180);
doc.path(`M10 315
L 110 215
@ -122,28 +123,28 @@ describe('vector', function () {
L 172.55 152.45
A 30 50 -45 0 1 215.1 109.9
L 315 10`);
doc.fillAndStroke('#73B373', '#000');
doc.fillAndStroke('#73B373', '#000');
doc.translate(0, 180);
doc.path(`M80 80
A 45 45, 0, 0, 0, 125 125
L 125 80 Z`);
doc.fillAndStroke('#73B373', '#000');
doc.fillAndStroke('#73B373', '#000');
doc.path(`M230 80
A 45 45, 0, 1, 0, 275 125
L 275 80 Z`);
doc.fillAndStroke('#F67676', '#000');
doc.fillAndStroke('#F67676', '#000');
doc.path(`M80 230
A 45 45, 0, 0, 1, 125 275
L 125 230 Z`);
doc.fillAndStroke('#AF6FAF', '#000');
doc.fillAndStroke('#AF6FAF', '#000');
doc.path(`M230 230
A 45 45, 0, 1, 1, 275 275
L 275 230 Z`);
doc.fillAndStroke('#6F6FEF', '#000');
doc.fillAndStroke('#6F6FEF', '#000');
});
});
});
});

View File

@ -1,20 +1,38 @@
const PDFDocument = require("../../lib/document").default;
const PDFDocument = require('../../lib/document').default;
describe("color", function() {
test("normalize", function() {
describe('color', function() {
test('normalize', function() {
const doc = new PDFDocument();
expect(doc._normalizeColor("#FFF")).toEqual([1, 1, 1]);
expect(doc._normalizeColor("#FFFFFF")).toEqual([1, 1, 1]);
expect(doc._normalizeColor("#000")).toEqual([0, 0, 0]);
expect(doc._normalizeColor("#000000")).toEqual([0, 0, 0]);
expect(doc._normalizeColor("#6F6FEF")).toEqual([0.43529411764705883, 0.43529411764705883, 0.9372549019607843]);
expect(doc._normalizeColor('#FFF')).toEqual([1, 1, 1]);
expect(doc._normalizeColor('#FFFFFF')).toEqual([1, 1, 1]);
expect(doc._normalizeColor('#000')).toEqual([0, 0, 0]);
expect(doc._normalizeColor('#000000')).toEqual([0, 0, 0]);
expect(doc._normalizeColor('#6F6FEF')).toEqual([
0.43529411764705883,
0.43529411764705883,
0.9372549019607843
]);
expect(doc._normalizeColor([255, 255, 255])).toEqual([1, 1, 1]);
expect(doc._normalizeColor([255, 255, 255, 255])).toEqual([2.55, 2.55, 2.55, 2.55]);
expect(doc._normalizeColor([0, 0, 0])).toEqual([0, 0, 0]);
expect(doc._normalizeColor([255, 255, 255, 255])).toEqual([
2.55,
2.55,
2.55,
2.55
]);
expect(doc._normalizeColor([0, 0, 0])).toEqual([0, 0, 0]);
expect(doc._normalizeColor([0, 0, 0, 0])).toEqual([0, 0, 0, 0]);
expect(doc._normalizeColor([128, 10, 18])).toEqual([0.5019607843137255, 0.0392156862745098, 0.07058823529411765]);
expect(doc._normalizeColor([128, 10, 18, 100])).toEqual([1.28, 0.1, 0.18, 1]);
expect(doc._normalizeColor([128, 10, 18])).toEqual([
0.5019607843137255,
0.0392156862745098,
0.07058823529411765
]);
expect(doc._normalizeColor([128, 10, 18, 100])).toEqual([
1.28,
0.1,
0.18,
1
]);
});
});

View File

@ -1,29 +1,29 @@
const PDFDocument = require("../../lib/document").default;
const PDFReference = require("../../lib/reference").default;
const PNGImage = require("../../lib/image/png").default;
const fs = require("fs");
const PDFDocument = require('../../lib/document').default;
const PDFReference = require('../../lib/reference').default;
const PNGImage = require('../../lib/image/png').default;
const fs = require('fs');
describe("PNGImage", () => {
describe('PNGImage', () => {
let document;
const createImage = fileName => {
const img = new PNGImage(fs.readFileSync(fileName), "I1");
const img = new PNGImage(fs.readFileSync(fileName), 'I1');
// noop data manipulation methods
img.loadIndexedAlphaChannel = () => {
if (img.image.transparency.indexed) {
img.alphaChannel = {};
img.finalize()
img.finalize();
}
};
img.splitAlphaChannel = () => {
if (img.image.hasAlphaChannel) {
img.alphaChannel = {};
img.finalize()
img.finalize();
}
};
const finalizeFn = img.finalize;
jest.spyOn(img, 'finalize').mockImplementation(() => finalizeFn.call(img));
img.embed(document);
img.embed(document);
return img;
};
@ -31,7 +31,7 @@ describe("PNGImage", () => {
document = new PDFDocument();
});
test("RGB", () => {
test('RGB', () => {
// ImageWidth = 400
// ImageHeight = 533
// BitDepth = 8
@ -40,18 +40,18 @@ describe("PNGImage", () => {
// Filter = 0
// Interlace = 0
const img = createImage("./demo/images/test2.png");
const img = createImage('./demo/images/test2.png');
expect(img.finalize).toBeCalledTimes(1);
expect(img.obj.data).toMatchObject({
BitsPerComponent: 8,
ColorSpace: "DeviceRGB",
Filter: "FlateDecode",
ColorSpace: 'DeviceRGB',
Filter: 'FlateDecode',
Height: 533,
Length: 397011,
Subtype: "Image",
Type: "XObject",
Subtype: 'Image',
Type: 'XObject',
Width: 400,
DecodeParms: expect.any(PDFReference)
});
@ -64,7 +64,7 @@ describe("PNGImage", () => {
});
});
test("RGB white transparent", () => {
test('RGB white transparent', () => {
// ImageWidth = 32
// ImageHeight = 32
// BitDepth = 16
@ -73,18 +73,20 @@ describe("PNGImage", () => {
// Filter = 0
// Interlace = 0
const img = createImage("./tests/images/pngsuite-rgb-transparent-white.png");
const img = createImage(
'./tests/images/pngsuite-rgb-transparent-white.png'
);
expect(img.finalize).toBeCalledTimes(1);
expect(img.obj.data).toMatchObject({
BitsPerComponent: 16,
ColorSpace: "DeviceRGB",
Filter: "FlateDecode",
ColorSpace: 'DeviceRGB',
Filter: 'FlateDecode',
Height: 32,
Length: 1932,
Subtype: "Image",
Type: "XObject",
Subtype: 'Image',
Type: 'XObject',
Width: 32,
Mask: [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
DecodeParms: expect.any(PDFReference)
@ -96,9 +98,9 @@ describe("PNGImage", () => {
Columns: 32,
Predictor: 15
});
});
});
test("RGB (8bit) with Alpha", () => {
test('RGB (8bit) with Alpha', () => {
// ImageWidth = 409
// ImageHeight = 400
// BitDepth = 8
@ -107,39 +109,36 @@ describe("PNGImage", () => {
// Filter = 0
// Interlace = 0
const img = createImage("./tests/images/bee.png");
const img = createImage('./tests/images/bee.png');
expect(img.finalize).toBeCalledTimes(1);
expect(img.obj.data).toMatchObject({
BitsPerComponent: 8,
ColorSpace: "DeviceRGB",
Filter: "FlateDecode",
ColorSpace: 'DeviceRGB',
Filter: 'FlateDecode',
Height: 400,
Length: 47715,
Subtype: "Image",
Type: "XObject",
Subtype: 'Image',
Type: 'XObject',
Width: 409,
SMask: expect.any(PDFReference)
});
expect(img.obj.data.SMask.data).toMatchObject({
BitsPerComponent: 8,
ColorSpace: "DeviceGray",
Decode: [
0,
1
],
Filter: "FlateDecode",
ColorSpace: 'DeviceGray',
Decode: [0, 1],
Filter: 'FlateDecode',
Height: 400,
Length: 16,
Subtype: "Image",
Type: "XObject",
Width: 409,
Subtype: 'Image',
Type: 'XObject',
Width: 409
});
});
test("RGB (16bit) with Alpha", () => {
test('RGB (16bit) with Alpha', () => {
// ImageWidth = 175
// ImageHeight = 65
// BitDepth = 16
@ -148,39 +147,36 @@ describe("PNGImage", () => {
// Filter = 0
// Interlace = 0
const img = createImage("./tests/images/straight.png");
const img = createImage('./tests/images/straight.png');
expect(img.finalize).toBeCalledTimes(1);
expect(img.obj.data).toMatchObject({
BitsPerComponent: 8,
ColorSpace: "DeviceRGB",
Filter: "FlateDecode",
ColorSpace: 'DeviceRGB',
Filter: 'FlateDecode',
Height: 65,
Length: 28537,
Subtype: "Image",
Type: "XObject",
Subtype: 'Image',
Type: 'XObject',
Width: 175,
SMask: expect.any(PDFReference)
});
expect(img.obj.data.SMask.data).toMatchObject({
BitsPerComponent: 8,
ColorSpace: "DeviceGray",
Decode: [
0,
1
],
Filter: "FlateDecode",
ColorSpace: 'DeviceGray',
Decode: [0, 1],
Filter: 'FlateDecode',
Height: 65,
Length: 16,
Subtype: "Image",
Type: "XObject",
Width: 175,
Subtype: 'Image',
Type: 'XObject',
Width: 175
});
});
});
test("Pallete", () => {
test('Pallete', () => {
// ImageWidth = 980
// ImageHeight = 540
// BitDepth = 8
@ -189,18 +185,18 @@ describe("PNGImage", () => {
// Filter = 0
// Interlace = 0
const img = createImage("./demo/images/test3.png");
const img = createImage('./demo/images/test3.png');
expect(img.finalize).toBeCalledTimes(1);
expect(img.obj.data).toMatchObject({
BitsPerComponent: 8,
ColorSpace: ["Indexed", "DeviceRGB", 255, expect.any(PDFReference)],
Filter: "FlateDecode",
ColorSpace: ['Indexed', 'DeviceRGB', 255, expect.any(PDFReference)],
Filter: 'FlateDecode',
Height: 540,
Length: 56682,
Subtype: "Image",
Type: "XObject",
Subtype: 'Image',
Type: 'XObject',
Width: 980,
DecodeParms: expect.any(PDFReference)
});
@ -213,7 +209,7 @@ describe("PNGImage", () => {
});
});
test("Pallete indexed transparency", () => {
test('Pallete indexed transparency', () => {
// ImageWidth = 32
// ImageHeight = 32
// BitDepth = 8
@ -222,21 +218,23 @@ describe("PNGImage", () => {
// Filter = 0
// Interlace = 0
const img = createImage("./tests/images/pngsuite-palette-transparent-white.png");
const img = createImage(
'./tests/images/pngsuite-palette-transparent-white.png'
);
expect(img.finalize).toBeCalledTimes(1);
expect(img.obj.data).toMatchObject({
BitsPerComponent: 8,
ColorSpace: ["Indexed", "DeviceRGB", 244, expect.any(PDFReference)],
Filter: "FlateDecode",
ColorSpace: ['Indexed', 'DeviceRGB', 244, expect.any(PDFReference)],
Filter: 'FlateDecode',
Height: 32,
Length: 650,
Subtype: "Image",
Type: "XObject",
Subtype: 'Image',
Type: 'XObject',
Width: 32,
DecodeParms: expect.any(PDFReference),
SMask: expect.any(PDFReference),
SMask: expect.any(PDFReference)
});
expect(img.obj.data.DecodeParms.data).toMatchObject({
@ -248,21 +246,18 @@ describe("PNGImage", () => {
expect(img.obj.data.SMask.data).toMatchObject({
BitsPerComponent: 8,
ColorSpace: "DeviceGray",
Decode: [
0,
1
],
Filter: "FlateDecode",
ColorSpace: 'DeviceGray',
Decode: [0, 1],
Filter: 'FlateDecode',
Height: 32,
Length: 16,
Subtype: "Image",
Type: "XObject",
Width: 32,
});
Subtype: 'Image',
Type: 'XObject',
Width: 32
});
});
test("Grayscale", () => {
test('Grayscale', () => {
// ImageWidth = 428
// ImageHeight = 320
// BitDepth = 8
@ -271,24 +266,24 @@ describe("PNGImage", () => {
// Filter = 0
// Interlace = 0
const img = createImage("./tests/images/glassware-noisy.png");
const img = createImage('./tests/images/glassware-noisy.png');
expect(img.finalize).toBeCalledTimes(1);
expect(img.obj.data).toMatchObject({
BitsPerComponent: 8,
ColorSpace: "DeviceGray",
Filter: "FlateDecode",
ColorSpace: 'DeviceGray',
Filter: 'FlateDecode',
Height: 428,
Length: 82633,
Subtype: "Image",
Type: "XObject",
Subtype: 'Image',
Type: 'XObject',
Width: 320,
DecodeParms: expect.any(PDFReference),
DecodeParms: expect.any(PDFReference)
});
});
test("Grayscale black transparent", () => {
test('Grayscale black transparent', () => {
// ImageWidth = 32
// ImageHeight = 32
// BitDepth = 4
@ -297,21 +292,23 @@ describe("PNGImage", () => {
// Filter = 0
// Interlace = 0
const img = createImage("./tests/images/pngsuite-gray-transparent-black.png");
const img = createImage(
'./tests/images/pngsuite-gray-transparent-black.png'
);
expect(img.finalize).toBeCalledTimes(1);
expect(img.obj.data).toMatchObject({
BitsPerComponent: 4,
ColorSpace: "DeviceGray",
Filter: "FlateDecode",
ColorSpace: 'DeviceGray',
Filter: 'FlateDecode',
Height: 32,
Length: 328,
Subtype: "Image",
Type: "XObject",
Subtype: 'Image',
Type: 'XObject',
Width: 32,
Mask: [0, 0],
DecodeParms: expect.any(PDFReference),
DecodeParms: expect.any(PDFReference)
});
expect(img.obj.data.DecodeParms.data).toMatchObject({
@ -319,10 +316,10 @@ describe("PNGImage", () => {
Colors: 1,
Columns: 32,
Predictor: 15
});
});
});
test("Grayscale white transparent", () => {
test('Grayscale white transparent', () => {
// ImageWidth = 32
// ImageHeight = 32
// BitDepth = 16
@ -331,21 +328,23 @@ describe("PNGImage", () => {
// Filter = 0
// Interlace = 0
const img = createImage("./tests/images/pngsuite-gray-transparent-white.png");
const img = createImage(
'./tests/images/pngsuite-gray-transparent-white.png'
);
expect(img.finalize).toBeCalledTimes(1);
expect(img.obj.data).toMatchObject({
BitsPerComponent: 16,
ColorSpace: "DeviceGray",
Filter: "FlateDecode",
ColorSpace: 'DeviceGray',
Filter: 'FlateDecode',
Height: 32,
Length: 1212,
Subtype: "Image",
Type: "XObject",
Subtype: 'Image',
Type: 'XObject',
Width: 32,
Mask: [255, 255],
DecodeParms: expect.any(PDFReference),
DecodeParms: expect.any(PDFReference)
});
expect(img.obj.data.DecodeParms.data).toMatchObject({
@ -354,9 +353,9 @@ describe("PNGImage", () => {
Columns: 32,
Predictor: 15
});
});
});
test("Grayscale with Alpha", () => {
test('Grayscale with Alpha', () => {
// ImageWidth = 112
// ImageHeight = 112
// BitDepth = 8
@ -365,35 +364,32 @@ describe("PNGImage", () => {
// Filter = 0
// Interlace = 0
const img = createImage("./tests/images/fish.png");
const img = createImage('./tests/images/fish.png');
expect(img.finalize).toBeCalledTimes(1);
expect(img.obj.data).toMatchObject({
BitsPerComponent: 8,
ColorSpace: "DeviceGray",
Filter: "FlateDecode",
ColorSpace: 'DeviceGray',
Filter: 'FlateDecode',
Height: 112,
Length: 9922,
Subtype: "Image",
Type: "XObject",
Subtype: 'Image',
Type: 'XObject',
Width: 112,
SMask: expect.any(PDFReference)
});
expect(img.obj.data.SMask.data).toMatchObject({
BitsPerComponent: 8,
ColorSpace: "DeviceGray",
Decode: [
0,
1
],
Filter: "FlateDecode",
ColorSpace: 'DeviceGray',
Decode: [0, 1],
Filter: 'FlateDecode',
Height: 112,
Length: 16,
Subtype: "Image",
Type: "XObject",
Width: 112,
Subtype: 'Image',
Type: 'XObject',
Width: 112
});
});
});

View File

@ -1,52 +1,48 @@
const PDFReference = require("../../lib/reference").default;
const PDFDocument = require("../../lib/document").default;
const zlib = require('zlib')
const PDFReference = require('../../lib/reference').default;
const PDFDocument = require('../../lib/document').default;
const zlib = require('zlib');
describe('PDFReference', () => {
let document
let document;
beforeEach(() => {
document = new PDFDocument()
})
document = new PDFDocument();
});
test('instantiated without data', () => {
const ref = new PDFReference(document, 1);
expect(ref.id).toBeDefined()
expect(ref.data).toBeDefined()
expect(ref.data).toBeInstanceOf(Object)
})
expect(ref.id).toBeDefined();
expect(ref.data).toBeDefined();
expect(ref.data).toBeInstanceOf(Object);
});
test('instantiated with data', () => {
const refData = {Pages: 0}
const ref = new PDFReference(document, 1, refData);
const refData = { Pages: 0 };
const ref = new PDFReference(document, 1, refData);
expect(ref.id).toBe(1)
expect(ref.data).toBe(refData)
})
expect(ref.id).toBe(1);
expect(ref.data).toBe(refData);
});
test('written data of empty reference', (done) => {
const dataLog = []
const expected = [
'1 0 obj',
'<<\n>>',
'endobj'
]
test('written data of empty reference', done => {
const dataLog = [];
const expected = ['1 0 obj', '<<\n>>', 'endobj'];
const ref = new PDFReference(document, 1);
document._write = function(data) {
dataLog.push(data)
}
ref.finalize()
dataLog.push(data);
};
ref.finalize();
setTimeout(() => {
expect(dataLog).toEqual(expected)
done()
}, 1)
})
expect(dataLog).toEqual(expected);
done();
}, 1);
});
test('written data of reference with uncompressed data', (done) => {
const dataLog = []
const chunk = new Buffer('test')
test('written data of reference with uncompressed data', done => {
const dataLog = [];
const chunk = new Buffer('test');
const expected = [
'1 0 obj',
'1 0 obj',
`<<
/Length ${chunk.length}
>>`,
@ -54,26 +50,26 @@ describe('PDFReference', () => {
chunk,
'\nendstream',
'endobj'
]
];
const ref = new PDFReference(document, 1);
ref.compress = false
ref.write(chunk)
ref.compress = false;
ref.write(chunk);
document._write = function(data) {
dataLog.push(data)
}
ref.finalize()
dataLog.push(data);
};
ref.finalize();
setTimeout(() => {
expect(dataLog).toEqual(expected)
done()
}, 1)
})
expect(dataLog).toEqual(expected);
done();
}, 1);
});
test('written data of reference with compressed data', (done) => {
const dataLog = []
const chunk = new Buffer('test')
test('written data of reference with compressed data', done => {
const dataLog = [];
const chunk = new Buffer('test');
const compressed = zlib.deflateSync(chunk);
const expected = [
'1 0 obj',
'1 0 obj',
`<<
/Length ${compressed.length}
/Filter /FlateDecode
@ -82,16 +78,16 @@ describe('PDFReference', () => {
compressed,
'\nendstream',
'endobj'
]
const ref = new PDFReference(document, 1);
ref.write(chunk)
];
const ref = new PDFReference(document, 1);
ref.write(chunk);
document._write = function(data) {
dataLog.push(data)
}
ref.finalize()
dataLog.push(data);
};
ref.finalize();
setTimeout(() => {
expect(dataLog).toEqual(expected)
done()
}, 1)
})
})
expect(dataLog).toEqual(expected);
done();
}, 1);
});
});

View File

@ -4,42 +4,35 @@ const PDFSecurity = require('../../lib/security').default;
// manual mock for PDFSecurity to ensure stored id will be the same accross different systems
PDFSecurity.generateFileID = () => {
return new Buffer('mocked-pdf-id');
}
};
describe('Document trailer', () => {
let document;
beforeEach(() => {
document = new PDFDocument({info: { CreationDate: new Date(Date.UTC(2018,1,1)) } });
document = new PDFDocument({
info: { CreationDate: new Date(Date.UTC(2018, 1, 1)) }
});
});
test('', (done) => {
test('', done => {
const dataLog = [];
const expected = [
[
'7 0 obj',
'<<\n/Producer 8 0 R\n/Creator 9 0 R\n/CreationDate 10 0 R\n>>'
],
[
'8 0 obj',
'(PDFKit)'
],
[
'9 0 obj',
'(PDFKit)'
],
[
'10 0 obj',
'(D:20180201000000Z)'
],
['8 0 obj', '(PDFKit)'],
['9 0 obj', '(PDFKit)'],
['10 0 obj', '(D:20180201000000Z)'],
[
'trailer',
`<<\n/Size 11\n/Root 2 0 R\n/Info 7 0 R\n/ID [<6d6f636b65642d7064662d6964> <6d6f636b65642d7064662d6964>]\n>>`
]
];
document._write = function(data) {
dataLog.push(data)
}
dataLog.push(data);
};
document.end();
setTimeout(() => {
for (let i = 0; i < expected.length; ++i) {
@ -51,4 +44,4 @@ describe('Document trailer', () => {
done();
}, 1);
});
});
});