-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
Copy pathattachments.js
126 lines (114 loc) · 4.04 KB
/
attachments.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import fs from 'fs';
import CryptoJS from 'crypto-js';
export default {
/**
* Embed contents of `src` in PDF
* @param {Buffer | ArrayBuffer | string} src input Buffer, ArrayBuffer, base64 encoded string or path to file
* @param {object} options
* * options.name: filename to be shown in PDF, will use `src` if none set
* * options.type: filetype to be shown in PDF
* * options.description: description to be shown in PDF
* * options.hidden: if true, do not add attachment to EmbeddedFiles dictionary. Useful for file attachment annotations
* * options.creationDate: override creation date
* * options.modifiedDate: override modified date
* * options.relationship: Relationship between the PDF document and its attached file. Can be 'Alternative', 'Data', 'Source', 'Supplement' or 'Unspecified'.
* @returns filespec reference
*/
file(src, options = {}) {
options.name = options.name || src;
options.relationship = options.relationship || 'Unspecified';
const refBody = {
Type: 'EmbeddedFile',
Params: {},
};
let data;
if (!src) {
throw new Error('No src specified');
}
if (Buffer.isBuffer(src)) {
data = src;
} else if (src instanceof ArrayBuffer) {
data = Buffer.from(new Uint8Array(src));
} else {
let match;
if ((match = /^data:(.*?);base64,(.*)$/.exec(src))) {
if (match[1]) {
refBody.Subtype = match[1].replace('/', '#2F');
}
data = Buffer.from(match[2], 'base64');
} else {
data = fs.readFileSync(src);
if (!data) {
throw new Error(`Could not read contents of file at filepath ${src}`);
}
// update CreationDate and ModDate
const { birthtime, ctime } = fs.statSync(src);
refBody.Params.CreationDate = birthtime;
refBody.Params.ModDate = ctime;
}
}
// override creation date and modified date
if (options.creationDate instanceof Date) {
refBody.Params.CreationDate = options.creationDate;
}
if (options.modifiedDate instanceof Date) {
refBody.Params.ModDate = options.modifiedDate;
}
// add optional subtype
if (options.type) {
refBody.Subtype = options.type.replace('/', '#2F');
}
// add checksum and size information
const checksum = CryptoJS.MD5(
CryptoJS.lib.WordArray.create(new Uint8Array(data)),
);
refBody.Params.CheckSum = new String(checksum);
refBody.Params.Size = data.byteLength;
// save some space when embedding the same file again
// if a file with the same name and metadata exists, reuse its reference
let ref;
if (!this._fileRegistry) this._fileRegistry = {};
let file = this._fileRegistry[options.name];
if (file && isEqual(refBody, file)) {
ref = file.ref;
} else {
ref = this.ref(refBody);
ref.end(data);
this._fileRegistry[options.name] = { ...refBody, ref };
}
// add filespec for embedded file
const fileSpecBody = {
Type: 'Filespec',
AFRelationship: options.relationship,
F: new String(options.name),
EF: { F: ref },
UF: new String(options.name),
};
if (options.description) {
fileSpecBody.Desc = new String(options.description);
}
const filespec = this.ref(fileSpecBody);
filespec.end();
if (!options.hidden) {
this.addNamedEmbeddedFile(options.name, filespec);
}
// Add file to the catalogue to be PDF/A3 compliant
if (this._root.data.AF) {
this._root.data.AF.push(filespec);
} else {
this._root.data.AF = [filespec];
}
return filespec;
},
};
/** check two embedded file metadata objects for equality */
function isEqual(a, b) {
return (
a.Subtype === b.Subtype &&
a.Params.CheckSum.toString() === b.Params.CheckSum.toString() &&
a.Params.Size === b.Params.Size &&
a.Params.CreationDate.getTime() === b.Params.CreationDate.getTime() &&
((a.Params.ModDate === undefined && b.Params.ModDate === undefined) ||
a.Params.ModDate.getTime() === b.Params.ModDate.getTime())
);
}