mirror of
https://github.com/foliojs/pdfkit.git
synced 2025-12-08 20:15:54 +00:00
Add prettier npm script and run it in *.js files
This commit is contained in:
parent
8ee8aa93a1
commit
a6af76467c
3
.prettierrc
Normal file
3
.prettierrc
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"singleQuote": true
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
@ -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();
|
||||
|
||||
89
demo/test.js
89
demo/test.js
@ -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();
|
||||
|
||||
1741
demo/tiger.js
1741
demo/tiger.js
File diff suppressed because one or more lines are too long
134
docs/generate.js
134
docs/generate.js
@ -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;
|
||||
}
|
||||
|
||||
@ -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`,
|
||||
|
||||
@ -7,5 +7,5 @@ class PDFAbstractReference {
|
||||
throw new Error('Must be implemented by subclasses');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default PDFAbstractReference;
|
||||
|
||||
146
lib/data.js
146
lib/data.js
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
35
lib/font.js
35
lib/font.js
@ -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;
|
||||
|
||||
100
lib/font/afm.js
100
lib/font/afm.js
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
133
lib/gradient.js
133
lib/gradient.js
@ -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 };
|
||||
|
||||
19
lib/image.js
19
lib/image.js
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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];
|
||||
}
|
||||
};
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
},
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
69
lib/page.js
69
lib/page.js
@ -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;
|
||||
|
||||
231
lib/path.js
231
lib/path.js
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
249
lib/security.js
249
lib/security.js
@ -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;
|
||||
|
||||
@ -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
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -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
@ -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
@ -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');
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -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
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@ -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
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user