Skip to content

Commit 226467e

Browse files
authored
refactor: partially unify pdfkit (#3065)
1 parent 9564631 commit 226467e

File tree

12 files changed

+707
-76
lines changed

12 files changed

+707
-76
lines changed

.changeset/fair-cougars-reflect.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@react-pdf/pdfkit": patch
3+
"@react-pdf/render": patch
4+
"@react-pdf/renderer": patch
5+
---
6+
7+
refactor: partially unify pdfkit

packages/pdfkit/src/document.js

Lines changed: 42 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import AttachmentsMixin from './mixins/attachments';
2222
import LineWrapper from './line_wrapper';
2323
import SubsetMixin from './mixins/subsets';
2424
import MetadataMixin from './mixins/metadata';
25-
import capitalize from './utils/capitalize';
2625

2726
class PDFDocument extends stream.Readable {
2827
constructor(options = {}) {
@@ -61,7 +60,6 @@ class PDFDocument extends stream.Readable {
6160
this._waiting = 0;
6261
this._ended = false;
6362
this._offset = 0;
64-
6563
const Pages = this.ref({
6664
Type: 'Pages',
6765
Count: 0,
@@ -82,26 +80,19 @@ class PDFDocument extends stream.Readable {
8280
this._root.data.Lang = new String(this.options.lang);
8381
}
8482

85-
if (this.options.pageLayout) {
86-
this._root.data.PageLayout = capitalize(this.options.pageLayout);
87-
}
88-
89-
if (this.options.pageMode) {
90-
this._root.data.PageMode = capitalize(this.options.pageMode);
91-
}
92-
9383
// The current page
9484
this.page = null;
9585

9686
// Initialize mixins
87+
this.initMetadata();
9788
this.initColor();
9889
this.initVector();
99-
this.initFonts();
90+
this.initFonts(options.font);
10091
this.initText();
10192
this.initImages();
10293
this.initOutline();
103-
this.initSubset(options);
10494
this.initMarkings(options);
95+
this.initSubset(options);
10596

10697
// Initialize the metadata
10798
this.info = {
@@ -129,7 +120,8 @@ class PDFDocument extends stream.Readable {
129120
// Initialize security settings
130121
// this._security = PDFSecurity.create(this, options);
131122

132-
// Write the header PDF version
123+
// Write the header
124+
// PDF version
133125
this._write(`%PDF-${this.version}`);
134126

135127
// 4 binary chars, as recommended by the spec
@@ -142,7 +134,6 @@ class PDFDocument extends stream.Readable {
142134
}
143135

144136
addPage(options) {
145-
// end the current page if needed
146137
if (options == null) {
147138
({ options } = this);
148139
}
@@ -161,12 +152,16 @@ class PDFDocument extends stream.Readable {
161152
pages.Kids.push(this.page.dictionary);
162153
pages.Count++;
163154

155+
// reset x and y coordinates
156+
this.x = this.page.margins.left;
157+
this.y = this.page.margins.top;
158+
164159
// flip PDF coordinate system so that the origin is in
165160
// the top left rather than the bottom left
166161
this._ctm = [1, 0, 0, 1, 0, 0];
167162
this.transform(1, 0, 0, -1, 0, this.page.height);
168163

169-
// this.emit('pageAdded');
164+
this.emit('pageAdded');
170165

171166
return this;
172167
}
@@ -181,13 +176,30 @@ class PDFDocument extends stream.Readable {
181176
return this;
182177
}
183178

179+
bufferedPageRange() {
180+
return { start: this._pageBufferStart, count: this._pageBuffer.length };
181+
}
182+
183+
switchToPage(n) {
184+
let page;
185+
if (!(page = this._pageBuffer[n - this._pageBufferStart])) {
186+
throw new Error(
187+
`switchToPage(${n}) out of bounds, current buffer covers pages ${
188+
this._pageBufferStart
189+
} to ${this._pageBufferStart + this._pageBuffer.length - 1}`
190+
);
191+
}
192+
193+
return (this.page = page);
194+
}
195+
184196
flushPages() {
185197
// this local variable exists so we're future-proof against
186198
// reentrant calls to flushPages.
187199
const pages = this._pageBuffer;
188200
this._pageBuffer = [];
189201
this._pageBufferStart += pages.length;
190-
for (let page of Array.from(pages)) {
202+
for (let page of pages) {
191203
this.endPageMarkings(page);
192204
page.end();
193205
}
@@ -234,9 +246,8 @@ class PDFDocument extends stream.Readable {
234246
return ref;
235247
}
236248

237-
_read() {
238-
// do nothing, but this method is required by node
239-
}
249+
_read() {}
250+
// do nothing, but this method is required by node
240251

241252
_write(data) {
242253
if (!Buffer.isBuffer(data)) {
@@ -262,6 +273,7 @@ class PDFDocument extends stream.Readable {
262273

263274
end() {
264275
this.flushPages();
276+
265277
this._info = this.ref();
266278
for (let key in this.info) {
267279
let val = this.info[key];
@@ -289,6 +301,8 @@ class PDFDocument extends stream.Readable {
289301
this.endSubset();
290302
}
291303

304+
this.endMetadata();
305+
292306
this._root.end();
293307
this._root.data.Pages.end();
294308
this._root.data.Names.end();
@@ -298,15 +312,15 @@ class PDFDocument extends stream.Readable {
298312
this._root.data.ViewerPreferences.end();
299313
}
300314

301-
// if (this._security) {
302-
// this._security.end();
303-
// }
315+
if (this._security) {
316+
this._security.end();
317+
}
304318

305319
if (this._waiting === 0) {
306320
return this._finalize();
321+
} else {
322+
return (this._ended = true);
307323
}
308-
309-
this._ended = true;
310324
}
311325

312326
_finalize() {
@@ -316,7 +330,7 @@ class PDFDocument extends stream.Readable {
316330
this._write(`0 ${this._offsets.length + 1}`);
317331
this._write('0000000000 65535 f ');
318332

319-
for (let offset of Array.from(this._offsets)) {
333+
for (let offset of this._offsets) {
320334
offset = `0000000000${offset}`.slice(-10);
321335
this._write(offset + ' 00000 n ');
322336
}
@@ -328,10 +342,9 @@ class PDFDocument extends stream.Readable {
328342
Info: this._info,
329343
ID: [this._id, this._id]
330344
};
331-
332-
// if (this._security) {
333-
// trailer.Encrypt = this._security.dictionary;
334-
// }
345+
if (this._security) {
346+
trailer.Encrypt = this._security.dictionary;
347+
}
335348

336349
this._write('trailer');
337350
this._write(PDFObject.convert(trailer));
@@ -353,7 +366,6 @@ const mixin = (methods) => {
353366
Object.assign(PDFDocument.prototype, methods);
354367
};
355368

356-
// Load mixins
357369
mixin(MetadataMixin);
358370
mixin(ColorMixin);
359371
mixin(VectorMixin);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import fs from 'fs';
2+
import * as fontkit from 'fontkit';
3+
import StandardFont from './font/standard';
4+
import EmbeddedFont from './font/embedded';
5+
6+
class PDFFontFactory {
7+
static open(document, src, family, id) {
8+
let font;
9+
if (typeof src === 'string') {
10+
if (StandardFont.isStandardFont(src)) {
11+
return new StandardFont(document, src, id);
12+
}
13+
14+
src = fs.readFileSync(src);
15+
}
16+
if (src instanceof Uint8Array) {
17+
font = fontkit.create(src, family);
18+
} else if (src instanceof ArrayBuffer) {
19+
font = fontkit.create(new Uint8Array(src), family);
20+
}
21+
22+
if (font == null) {
23+
throw new Error('Not a supported font format or standard PDF font.');
24+
}
25+
26+
return new EmbeddedFont(document, font, id);
27+
}
28+
}
29+
30+
export default PDFFontFactory;

packages/pdfkit/src/mixins/attachments.js

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
import fs from 'fs';
2-
// This file is ran directly with Node - needs to have .js extension
3-
// eslint-disable-next-line import/extensions
4-
import * as CryptoJS from 'crypto-js/core.js';
5-
// This file is ran directly with Node - needs to have .js extension
6-
// eslint-disable-next-line import/extensions
7-
import MD5 from 'crypto-js/md5.js';
2+
import CryptoJS from 'crypto-js';
83

94
export default {
105
/**
@@ -17,10 +12,12 @@ export default {
1712
* * options.hidden: if true, do not add attachment to EmbeddedFiles dictionary. Useful for file attachment annotations
1813
* * options.creationDate: override creation date
1914
* * options.modifiedDate: override modified date
15+
* * options.relationship: Relationship between the PDF document and its attached file. Can be 'Alternative', 'Data', 'Source', 'Supplement' or 'Unspecified'.
2016
* @returns filespec reference
2117
*/
2218
file(src, options = {}) {
2319
options.name = options.name || src;
20+
options.relationship = options.relationship || 'Unspecified';
2421

2522
const refBody = {
2623
Type: 'EmbeddedFile',
@@ -37,12 +34,12 @@ export default {
3734
data = Buffer.from(new Uint8Array(src));
3835
} else {
3936
let match;
40-
if ((match = /^data:(.*);base64,(.*)$/.exec(src))) {
37+
if ((match = /^data:(.*?);base64,(.*)$/.exec(src))) {
4138
if (match[1]) {
4239
refBody.Subtype = match[1].replace('/', '#2F');
4340
}
4441
data = Buffer.from(match[2], 'base64');
45-
} else if (!BROWSER) {
42+
} else {
4643
data = fs.readFileSync(src);
4744
if (!data) {
4845
throw new Error(`Could not read contents of file at filepath ${src}`);
@@ -52,8 +49,6 @@ export default {
5249
const { birthtime, ctime } = fs.statSync(src);
5350
refBody.Params.CreationDate = birthtime;
5451
refBody.Params.ModDate = ctime;
55-
} else {
56-
throw new Error(`Could not find file ${src}`);
5752
}
5853
}
5954

@@ -70,7 +65,9 @@ export default {
7065
}
7166

7267
// add checksum and size information
73-
const checksum = MD5(CryptoJS.lib.WordArray.create(new Uint8Array(data)));
68+
const checksum = CryptoJS.MD5(
69+
CryptoJS.lib.WordArray.create(new Uint8Array(data))
70+
);
7471
refBody.Params.CheckSum = new String(checksum);
7572
refBody.Params.Size = data.byteLength;
7673

@@ -90,6 +87,7 @@ export default {
9087
// add filespec for embedded file
9188
const fileSpecBody = {
9289
Type: 'Filespec',
90+
AFRelationship: options.relationship,
9391
F: new String(options.name),
9492
EF: { F: ref },
9593
UF: new String(options.name)
@@ -104,6 +102,13 @@ export default {
104102
this.addNamedEmbeddedFile(options.name, filespec);
105103
}
106104

105+
// Add file to the catalogue to be PDF/A3 compliant
106+
if (this._root.data.AF) {
107+
this._root.data.AF.push(filespec);
108+
} else {
109+
this._root.data.AF = [filespec];
110+
}
111+
107112
return filespec;
108113
}
109114
};
@@ -114,7 +119,8 @@ function isEqual(a, b) {
114119
a.Subtype === b.Subtype &&
115120
a.Params.CheckSum.toString() === b.Params.CheckSum.toString() &&
116121
a.Params.Size === b.Params.Size &&
117-
a.Params.CreationDate === b.Params.CreationDate &&
118-
a.Params.ModDate === b.Params.ModDate
122+
a.Params.CreationDate.getTime() === b.Params.CreationDate.getTime() &&
123+
((a.Params.ModDate === undefined && b.Params.ModDate === undefined) ||
124+
a.Params.ModDate.getTime() === b.Params.ModDate.getTime())
119125
);
120126
}

0 commit comments

Comments
 (0)