UNPKG

184 kBJavaScriptView Raw
1'use strict';
2
3var stream = require('stream');
4var zlib = require('zlib');
5var CryptoJS = require('crypto-js');
6var fs = require('fs');
7var fontkit = require('fontkit');
8var events = require('events');
9var LineBreaker = require('linebreak');
10var exif = require('jpeg-exif');
11var PNG = require('png-js');
12
13/*
14PDFAbstractReference - abstract class for PDF reference
15*/
16
17class PDFAbstractReference {
18 toString() {
19 throw new Error('Must be implemented by subclasses');
20 }
21}
22
23/*
24PDFTree - abstract base class for name and number tree objects
25*/
26class PDFTree {
27 constructor(options = {}) {
28 this._items = {};
29 // disable /Limits output for this tree
30 this.limits = typeof options.limits === 'boolean' ? options.limits : true;
31 }
32 add(key, val) {
33 return this._items[key] = val;
34 }
35 get(key) {
36 return this._items[key];
37 }
38 toString() {
39 // Needs to be sorted by key
40 const sortedKeys = Object.keys(this._items).sort((a, b) => this._compareKeys(a, b));
41 const out = ['<<'];
42 if (this.limits && sortedKeys.length > 1) {
43 const first = sortedKeys[0],
44 last = sortedKeys[sortedKeys.length - 1];
45 out.push(` /Limits ${PDFObject.convert([this._dataForKey(first), this._dataForKey(last)])}`);
46 }
47 out.push(` /${this._keysName()} [`);
48 for (let key of sortedKeys) {
49 out.push(` ${PDFObject.convert(this._dataForKey(key))} ${PDFObject.convert(this._items[key])}`);
50 }
51 out.push(']');
52 out.push('>>');
53 return out.join('\n');
54 }
55 _compareKeys( /*a, b*/
56 ) {
57 throw new Error('Must be implemented by subclasses');
58 }
59 _keysName() {
60 throw new Error('Must be implemented by subclasses');
61 }
62 _dataForKey( /*k*/
63 ) {
64 throw new Error('Must be implemented by subclasses');
65 }
66}
67
68/*
69PDFObject - converts JavaScript types into their corresponding PDF types.
70By Devon Govett
71*/
72const pad = (str, length) => (Array(length + 1).join('0') + str).slice(-length);
73const escapableRe = /[\n\r\t\b\f()\\]/g;
74const escapable = {
75 '\n': '\\n',
76 '\r': '\\r',
77 '\t': '\\t',
78 '\b': '\\b',
79 '\f': '\\f',
80 '\\': '\\\\',
81 '(': '\\(',
82 ')': '\\)'
83};
84
85// Convert little endian UTF-16 to big endian
86const swapBytes = function (buff) {
87 const l = buff.length;
88 if (l & 0x01) {
89 throw new Error('Buffer length must be even');
90 } else {
91 for (let i = 0, end = l - 1; i < end; i += 2) {
92 const a = buff[i];
93 buff[i] = buff[i + 1];
94 buff[i + 1] = a;
95 }
96 }
97 return buff;
98};
99class PDFObject {
100 static convert(object, encryptFn = null) {
101 // String literals are converted to the PDF name type
102 if (typeof object === 'string') {
103 return `/${object}`;
104
105 // String objects are converted to PDF strings (UTF-16)
106 } else if (object instanceof String) {
107 let string = object;
108 // Detect if this is a unicode string
109 let isUnicode = false;
110 for (let i = 0, end = string.length; i < end; i++) {
111 if (string.charCodeAt(i) > 0x7f) {
112 isUnicode = true;
113 break;
114 }
115 }
116
117 // If so, encode it as big endian UTF-16
118 let stringBuffer;
119 if (isUnicode) {
120 stringBuffer = swapBytes(Buffer.from(`\ufeff${string}`, 'utf16le'));
121 } else {
122 stringBuffer = Buffer.from(string.valueOf(), 'ascii');
123 }
124
125 // Encrypt the string when necessary
126 if (encryptFn) {
127 string = encryptFn(stringBuffer).toString('binary');
128 } else {
129 string = stringBuffer.toString('binary');
130 }
131
132 // Escape characters as required by the spec
133 string = string.replace(escapableRe, c => escapable[c]);
134 return `(${string})`;
135
136 // Buffers are converted to PDF hex strings
137 } else if (Buffer.isBuffer(object)) {
138 return `<${object.toString('hex')}>`;
139 } else if (object instanceof PDFAbstractReference || object instanceof PDFTree) {
140 return object.toString();
141 } else if (object instanceof Date) {
142 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';
143
144 // Encrypt the string when necessary
145 if (encryptFn) {
146 string = encryptFn(Buffer.from(string, 'ascii')).toString('binary');
147
148 // Escape characters as required by the spec
149 string = string.replace(escapableRe, c => escapable[c]);
150 }
151 return `(${string})`;
152 } else if (Array.isArray(object)) {
153 const items = object.map(e => PDFObject.convert(e, encryptFn)).join(' ');
154 return `[${items}]`;
155 } else if ({}.toString.call(object) === '[object Object]') {
156 const out = ['<<'];
157 for (let key in object) {
158 const val = object[key];
159 out.push(`/${key} ${PDFObject.convert(val, encryptFn)}`);
160 }
161 out.push('>>');
162 return out.join('\n');
163 } else if (typeof object === 'number') {
164 return PDFObject.number(object);
165 } else {
166 return `${object}`;
167 }
168 }
169 static number(n) {
170 if (n > -1e21 && n < 1e21) {
171 return Math.round(n * 1e6) / 1e6;
172 }
173 throw new Error(`unsupported number: ${n}`);
174 }
175}
176
177/*
178PDFReference - represents a reference to another object in the PDF object heirarchy
179By Devon Govett
180*/
181class PDFReference extends PDFAbstractReference {
182 constructor(document, id, data = {}) {
183 super();
184 this.document = document;
185 this.id = id;
186 this.data = data;
187 this.gen = 0;
188 this.compress = this.document.compress && !this.data.Filter;
189 this.uncompressedLength = 0;
190 this.buffer = [];
191 }
192 write(chunk) {
193 if (!Buffer.isBuffer(chunk)) {
194 chunk = Buffer.from(chunk + '\n', 'binary');
195 }
196 this.uncompressedLength += chunk.length;
197 if (this.data.Length == null) {
198 this.data.Length = 0;
199 }
200 this.buffer.push(chunk);
201 this.data.Length += chunk.length;
202 if (this.compress) {
203 return this.data.Filter = 'FlateDecode';
204 }
205 }
206 end(chunk) {
207 if (chunk) {
208 this.write(chunk);
209 }
210 return this.finalize();
211 }
212 finalize() {
213 this.offset = this.document._offset;
214 const encryptFn = this.document._security ? this.document._security.getEncryptFn(this.id, this.gen) : null;
215 if (this.buffer.length) {
216 this.buffer = Buffer.concat(this.buffer);
217 if (this.compress) {
218 this.buffer = zlib.deflateSync(this.buffer);
219 }
220 if (encryptFn) {
221 this.buffer = encryptFn(this.buffer);
222 }
223 this.data.Length = this.buffer.length;
224 }
225 this.document._write(`${this.id} ${this.gen} obj`);
226 this.document._write(PDFObject.convert(this.data, encryptFn));
227 if (this.buffer.length) {
228 this.document._write('stream');
229 this.document._write(this.buffer);
230 this.buffer = []; // free up memory
231 this.document._write('\nendstream');
232 }
233 this.document._write('endobj');
234 this.document._refEnd(this);
235 }
236 toString() {
237 return `${this.id} ${this.gen} R`;
238 }
239}
240
241/*
242PDFPage - represents a single page in the PDF document
243By Devon Govett
244*/
245
246const DEFAULT_MARGINS = {
247 top: 72,
248 left: 72,
249 bottom: 72,
250 right: 72
251};
252const SIZES = {
253 '4A0': [4767.87, 6740.79],
254 '2A0': [3370.39, 4767.87],
255 A0: [2383.94, 3370.39],
256 A1: [1683.78, 2383.94],
257 A2: [1190.55, 1683.78],
258 A3: [841.89, 1190.55],
259 A4: [595.28, 841.89],
260 A5: [419.53, 595.28],
261 A6: [297.64, 419.53],
262 A7: [209.76, 297.64],
263 A8: [147.4, 209.76],
264 A9: [104.88, 147.4],
265 A10: [73.7, 104.88],
266 B0: [2834.65, 4008.19],
267 B1: [2004.09, 2834.65],
268 B2: [1417.32, 2004.09],
269 B3: [1000.63, 1417.32],
270 B4: [708.66, 1000.63],
271 B5: [498.9, 708.66],
272 B6: [354.33, 498.9],
273 B7: [249.45, 354.33],
274 B8: [175.75, 249.45],
275 B9: [124.72, 175.75],
276 B10: [87.87, 124.72],
277 C0: [2599.37, 3676.54],
278 C1: [1836.85, 2599.37],
279 C2: [1298.27, 1836.85],
280 C3: [918.43, 1298.27],
281 C4: [649.13, 918.43],
282 C5: [459.21, 649.13],
283 C6: [323.15, 459.21],
284 C7: [229.61, 323.15],
285 C8: [161.57, 229.61],
286 C9: [113.39, 161.57],
287 C10: [79.37, 113.39],
288 RA0: [2437.8, 3458.27],
289 RA1: [1729.13, 2437.8],
290 RA2: [1218.9, 1729.13],
291 RA3: [864.57, 1218.9],
292 RA4: [609.45, 864.57],
293 SRA0: [2551.18, 3628.35],
294 SRA1: [1814.17, 2551.18],
295 SRA2: [1275.59, 1814.17],
296 SRA3: [907.09, 1275.59],
297 SRA4: [637.8, 907.09],
298 EXECUTIVE: [521.86, 756.0],
299 FOLIO: [612.0, 936.0],
300 LEGAL: [612.0, 1008.0],
301 LETTER: [612.0, 792.0],
302 TABLOID: [792.0, 1224.0]
303};
304class PDFPage {
305 constructor(document, options = {}) {
306 this.document = document;
307 this.size = options.size || 'letter';
308 this.layout = options.layout || 'portrait';
309
310 // process margins
311 if (typeof options.margin === 'number') {
312 this.margins = {
313 top: options.margin,
314 left: options.margin,
315 bottom: options.margin,
316 right: options.margin
317 };
318
319 // default to 1 inch margins
320 } else {
321 this.margins = options.margins || DEFAULT_MARGINS;
322 }
323
324 // calculate page dimensions
325 const dimensions = Array.isArray(this.size) ? this.size : SIZES[this.size.toUpperCase()];
326 this.width = dimensions[this.layout === 'portrait' ? 0 : 1];
327 this.height = dimensions[this.layout === 'portrait' ? 1 : 0];
328 this.content = this.document.ref();
329
330 // Initialize the Font, XObject, and ExtGState dictionaries
331 this.resources = this.document.ref({
332 ProcSet: ['PDF', 'Text', 'ImageB', 'ImageC', 'ImageI']
333 });
334
335 // The page dictionary
336 this.dictionary = this.document.ref({
337 Type: 'Page',
338 Parent: this.document._root.data.Pages,
339 MediaBox: [0, 0, this.width, this.height],
340 Contents: this.content,
341 Resources: this.resources
342 });
343 this.markings = [];
344 }
345
346 // Lazily create these objects
347 get fonts() {
348 const data = this.resources.data;
349 return data.Font != null ? data.Font : data.Font = {};
350 }
351 get xobjects() {
352 const data = this.resources.data;
353 return data.XObject != null ? data.XObject : data.XObject = {};
354 }
355 get ext_gstates() {
356 const data = this.resources.data;
357 return data.ExtGState != null ? data.ExtGState : data.ExtGState = {};
358 }
359 get patterns() {
360 const data = this.resources.data;
361 return data.Pattern != null ? data.Pattern : data.Pattern = {};
362 }
363 get colorSpaces() {
364 const data = this.resources.data;
365 return data.ColorSpace || (data.ColorSpace = {});
366 }
367 get annotations() {
368 const data = this.dictionary.data;
369 return data.Annots != null ? data.Annots : data.Annots = [];
370 }
371 get structParentTreeKey() {
372 const data = this.dictionary.data;
373 return data.StructParents != null ? data.StructParents : data.StructParents = this.document.createStructParentTreeNextKey();
374 }
375 maxY() {
376 return this.height - this.margins.bottom;
377 }
378 write(chunk) {
379 return this.content.write(chunk);
380 }
381 end() {
382 this.dictionary.end();
383 this.resources.end();
384 return this.content.end();
385 }
386}
387
388/*
389PDFNameTree - represents a name tree object
390*/
391class PDFNameTree extends PDFTree {
392 _compareKeys(a, b) {
393 return a.localeCompare(b);
394 }
395 _keysName() {
396 return "Names";
397 }
398 _dataForKey(k) {
399 return new String(k);
400 }
401}
402
403/**
404 * Check if value is in a range group.
405 * @param {number} value
406 * @param {number[]} rangeGroup
407 * @returns {boolean}
408 */
409function inRange(value, rangeGroup) {
410 if (value < rangeGroup[0]) return false;
411 let startRange = 0;
412 let endRange = rangeGroup.length / 2;
413 while (startRange <= endRange) {
414 const middleRange = Math.floor((startRange + endRange) / 2);
415
416 // actual array index
417 const arrayIndex = middleRange * 2;
418
419 // Check if value is in range pointed by actual index
420 if (value >= rangeGroup[arrayIndex] && value <= rangeGroup[arrayIndex + 1]) {
421 return true;
422 }
423 if (value > rangeGroup[arrayIndex + 1]) {
424 // Search Right Side Of Array
425 startRange = middleRange + 1;
426 } else {
427 // Search Left Side Of Array
428 endRange = middleRange - 1;
429 }
430 }
431 return false;
432}
433
434// prettier-ignore-start
435/**
436 * A.1 Unassigned code points in Unicode 3.2
437 * @link https://tools.ietf.org/html/rfc3454#appendix-A.1
438 */
439const unassigned_code_points = [0x0221, 0x0221, 0x0234, 0x024f, 0x02ae, 0x02af, 0x02ef, 0x02ff, 0x0350, 0x035f, 0x0370, 0x0373, 0x0376, 0x0379, 0x037b, 0x037d, 0x037f, 0x0383, 0x038b, 0x038b, 0x038d, 0x038d, 0x03a2, 0x03a2, 0x03cf, 0x03cf, 0x03f7, 0x03ff, 0x0487, 0x0487, 0x04cf, 0x04cf, 0x04f6, 0x04f7, 0x04fa, 0x04ff, 0x0510, 0x0530, 0x0557, 0x0558, 0x0560, 0x0560, 0x0588, 0x0588, 0x058b, 0x0590, 0x05a2, 0x05a2, 0x05ba, 0x05ba, 0x05c5, 0x05cf, 0x05eb, 0x05ef, 0x05f5, 0x060b, 0x060d, 0x061a, 0x061c, 0x061e, 0x0620, 0x0620, 0x063b, 0x063f, 0x0656, 0x065f, 0x06ee, 0x06ef, 0x06ff, 0x06ff, 0x070e, 0x070e, 0x072d, 0x072f, 0x074b, 0x077f, 0x07b2, 0x0900, 0x0904, 0x0904, 0x093a, 0x093b, 0x094e, 0x094f, 0x0955, 0x0957, 0x0971, 0x0980, 0x0984, 0x0984, 0x098d, 0x098e, 0x0991, 0x0992, 0x09a9, 0x09a9, 0x09b1, 0x09b1, 0x09b3, 0x09b5, 0x09ba, 0x09bb, 0x09bd, 0x09bd, 0x09c5, 0x09c6, 0x09c9, 0x09ca, 0x09ce, 0x09d6, 0x09d8, 0x09db, 0x09de, 0x09de, 0x09e4, 0x09e5, 0x09fb, 0x0a01, 0x0a03, 0x0a04, 0x0a0b, 0x0a0e, 0x0a11, 0x0a12, 0x0a29, 0x0a29, 0x0a31, 0x0a31, 0x0a34, 0x0a34, 0x0a37, 0x0a37, 0x0a3a, 0x0a3b, 0x0a3d, 0x0a3d, 0x0a43, 0x0a46, 0x0a49, 0x0a4a, 0x0a4e, 0x0a58, 0x0a5d, 0x0a5d, 0x0a5f, 0x0a65, 0x0a75, 0x0a80, 0x0a84, 0x0a84, 0x0a8c, 0x0a8c, 0x0a8e, 0x0a8e, 0x0a92, 0x0a92, 0x0aa9, 0x0aa9, 0x0ab1, 0x0ab1, 0x0ab4, 0x0ab4, 0x0aba, 0x0abb, 0x0ac6, 0x0ac6, 0x0aca, 0x0aca, 0x0ace, 0x0acf, 0x0ad1, 0x0adf, 0x0ae1, 0x0ae5, 0x0af0, 0x0b00, 0x0b04, 0x0b04, 0x0b0d, 0x0b0e, 0x0b11, 0x0b12, 0x0b29, 0x0b29, 0x0b31, 0x0b31, 0x0b34, 0x0b35, 0x0b3a, 0x0b3b, 0x0b44, 0x0b46, 0x0b49, 0x0b4a, 0x0b4e, 0x0b55, 0x0b58, 0x0b5b, 0x0b5e, 0x0b5e, 0x0b62, 0x0b65, 0x0b71, 0x0b81, 0x0b84, 0x0b84, 0x0b8b, 0x0b8d, 0x0b91, 0x0b91, 0x0b96, 0x0b98, 0x0b9b, 0x0b9b, 0x0b9d, 0x0b9d, 0x0ba0, 0x0ba2, 0x0ba5, 0x0ba7, 0x0bab, 0x0bad, 0x0bb6, 0x0bb6, 0x0bba, 0x0bbd, 0x0bc3, 0x0bc5, 0x0bc9, 0x0bc9, 0x0bce, 0x0bd6, 0x0bd8, 0x0be6, 0x0bf3, 0x0c00, 0x0c04, 0x0c04, 0x0c0d, 0x0c0d, 0x0c11, 0x0c11, 0x0c29, 0x0c29, 0x0c34, 0x0c34, 0x0c3a, 0x0c3d, 0x0c45, 0x0c45, 0x0c49, 0x0c49, 0x0c4e, 0x0c54, 0x0c57, 0x0c5f, 0x0c62, 0x0c65, 0x0c70, 0x0c81, 0x0c84, 0x0c84, 0x0c8d, 0x0c8d, 0x0c91, 0x0c91, 0x0ca9, 0x0ca9, 0x0cb4, 0x0cb4, 0x0cba, 0x0cbd, 0x0cc5, 0x0cc5, 0x0cc9, 0x0cc9, 0x0cce, 0x0cd4, 0x0cd7, 0x0cdd, 0x0cdf, 0x0cdf, 0x0ce2, 0x0ce5, 0x0cf0, 0x0d01, 0x0d04, 0x0d04, 0x0d0d, 0x0d0d, 0x0d11, 0x0d11, 0x0d29, 0x0d29, 0x0d3a, 0x0d3d, 0x0d44, 0x0d45, 0x0d49, 0x0d49, 0x0d4e, 0x0d56, 0x0d58, 0x0d5f, 0x0d62, 0x0d65, 0x0d70, 0x0d81, 0x0d84, 0x0d84, 0x0d97, 0x0d99, 0x0db2, 0x0db2, 0x0dbc, 0x0dbc, 0x0dbe, 0x0dbf, 0x0dc7, 0x0dc9, 0x0dcb, 0x0dce, 0x0dd5, 0x0dd5, 0x0dd7, 0x0dd7, 0x0de0, 0x0df1, 0x0df5, 0x0e00, 0x0e3b, 0x0e3e, 0x0e5c, 0x0e80, 0x0e83, 0x0e83, 0x0e85, 0x0e86, 0x0e89, 0x0e89, 0x0e8b, 0x0e8c, 0x0e8e, 0x0e93, 0x0e98, 0x0e98, 0x0ea0, 0x0ea0, 0x0ea4, 0x0ea4, 0x0ea6, 0x0ea6, 0x0ea8, 0x0ea9, 0x0eac, 0x0eac, 0x0eba, 0x0eba, 0x0ebe, 0x0ebf, 0x0ec5, 0x0ec5, 0x0ec7, 0x0ec7, 0x0ece, 0x0ecf, 0x0eda, 0x0edb, 0x0ede, 0x0eff, 0x0f48, 0x0f48, 0x0f6b, 0x0f70, 0x0f8c, 0x0f8f, 0x0f98, 0x0f98, 0x0fbd, 0x0fbd, 0x0fcd, 0x0fce, 0x0fd0, 0x0fff, 0x1022, 0x1022, 0x1028, 0x1028, 0x102b, 0x102b, 0x1033, 0x1035, 0x103a, 0x103f, 0x105a, 0x109f, 0x10c6, 0x10cf, 0x10f9, 0x10fa, 0x10fc, 0x10ff, 0x115a, 0x115e, 0x11a3, 0x11a7, 0x11fa, 0x11ff, 0x1207, 0x1207, 0x1247, 0x1247, 0x1249, 0x1249, 0x124e, 0x124f, 0x1257, 0x1257, 0x1259, 0x1259, 0x125e, 0x125f, 0x1287, 0x1287, 0x1289, 0x1289, 0x128e, 0x128f, 0x12af, 0x12af, 0x12b1, 0x12b1, 0x12b6, 0x12b7, 0x12bf, 0x12bf, 0x12c1, 0x12c1, 0x12c6, 0x12c7, 0x12cf, 0x12cf, 0x12d7, 0x12d7, 0x12ef, 0x12ef, 0x130f, 0x130f, 0x1311, 0x1311, 0x1316, 0x1317, 0x131f, 0x131f, 0x1347, 0x1347, 0x135b, 0x1360, 0x137d, 0x139f, 0x13f5, 0x1400, 0x1677, 0x167f, 0x169d, 0x169f, 0x16f1, 0x16ff, 0x170d, 0x170d, 0x1715, 0x171f, 0x1737, 0x173f, 0x1754, 0x175f, 0x176d, 0x176d, 0x1771, 0x1771, 0x1774, 0x177f, 0x17dd, 0x17df, 0x17ea, 0x17ff, 0x180f, 0x180f, 0x181a, 0x181f, 0x1878, 0x187f, 0x18aa, 0x1dff, 0x1e9c, 0x1e9f, 0x1efa, 0x1eff, 0x1f16, 0x1f17, 0x1f1e, 0x1f1f, 0x1f46, 0x1f47, 0x1f4e, 0x1f4f, 0x1f58, 0x1f58, 0x1f5a, 0x1f5a, 0x1f5c, 0x1f5c, 0x1f5e, 0x1f5e, 0x1f7e, 0x1f7f, 0x1fb5, 0x1fb5, 0x1fc5, 0x1fc5, 0x1fd4, 0x1fd5, 0x1fdc, 0x1fdc, 0x1ff0, 0x1ff1, 0x1ff5, 0x1ff5, 0x1fff, 0x1fff, 0x2053, 0x2056, 0x2058, 0x205e, 0x2064, 0x2069, 0x2072, 0x2073, 0x208f, 0x209f, 0x20b2, 0x20cf, 0x20eb, 0x20ff, 0x213b, 0x213c, 0x214c, 0x2152, 0x2184, 0x218f, 0x23cf, 0x23ff, 0x2427, 0x243f, 0x244b, 0x245f, 0x24ff, 0x24ff, 0x2614, 0x2615, 0x2618, 0x2618, 0x267e, 0x267f, 0x268a, 0x2700, 0x2705, 0x2705, 0x270a, 0x270b, 0x2728, 0x2728, 0x274c, 0x274c, 0x274e, 0x274e, 0x2753, 0x2755, 0x2757, 0x2757, 0x275f, 0x2760, 0x2795, 0x2797, 0x27b0, 0x27b0, 0x27bf, 0x27cf, 0x27ec, 0x27ef, 0x2b00, 0x2e7f, 0x2e9a, 0x2e9a, 0x2ef4, 0x2eff, 0x2fd6, 0x2fef, 0x2ffc, 0x2fff, 0x3040, 0x3040, 0x3097, 0x3098, 0x3100, 0x3104, 0x312d, 0x3130, 0x318f, 0x318f, 0x31b8, 0x31ef, 0x321d, 0x321f, 0x3244, 0x3250, 0x327c, 0x327e, 0x32cc, 0x32cf, 0x32ff, 0x32ff, 0x3377, 0x337a, 0x33de, 0x33df, 0x33ff, 0x33ff, 0x4db6, 0x4dff, 0x9fa6, 0x9fff, 0xa48d, 0xa48f, 0xa4c7, 0xabff, 0xd7a4, 0xd7ff, 0xfa2e, 0xfa2f, 0xfa6b, 0xfaff, 0xfb07, 0xfb12, 0xfb18, 0xfb1c, 0xfb37, 0xfb37, 0xfb3d, 0xfb3d, 0xfb3f, 0xfb3f, 0xfb42, 0xfb42, 0xfb45, 0xfb45, 0xfbb2, 0xfbd2, 0xfd40, 0xfd4f, 0xfd90, 0xfd91, 0xfdc8, 0xfdcf, 0xfdfd, 0xfdff, 0xfe10, 0xfe1f, 0xfe24, 0xfe2f, 0xfe47, 0xfe48, 0xfe53, 0xfe53, 0xfe67, 0xfe67, 0xfe6c, 0xfe6f, 0xfe75, 0xfe75, 0xfefd, 0xfefe, 0xff00, 0xff00, 0xffbf, 0xffc1, 0xffc8, 0xffc9, 0xffd0, 0xffd1, 0xffd8, 0xffd9, 0xffdd, 0xffdf, 0xffe7, 0xffe7, 0xffef, 0xfff8, 0x10000, 0x102ff, 0x1031f, 0x1031f, 0x10324, 0x1032f, 0x1034b, 0x103ff, 0x10426, 0x10427, 0x1044e, 0x1cfff, 0x1d0f6, 0x1d0ff, 0x1d127, 0x1d129, 0x1d1de, 0x1d3ff, 0x1d455, 0x1d455, 0x1d49d, 0x1d49d, 0x1d4a0, 0x1d4a1, 0x1d4a3, 0x1d4a4, 0x1d4a7, 0x1d4a8, 0x1d4ad, 0x1d4ad, 0x1d4ba, 0x1d4ba, 0x1d4bc, 0x1d4bc, 0x1d4c1, 0x1d4c1, 0x1d4c4, 0x1d4c4, 0x1d506, 0x1d506, 0x1d50b, 0x1d50c, 0x1d515, 0x1d515, 0x1d51d, 0x1d51d, 0x1d53a, 0x1d53a, 0x1d53f, 0x1d53f, 0x1d545, 0x1d545, 0x1d547, 0x1d549, 0x1d551, 0x1d551, 0x1d6a4, 0x1d6a7, 0x1d7ca, 0x1d7cd, 0x1d800, 0x1fffd, 0x2a6d7, 0x2f7ff, 0x2fa1e, 0x2fffd, 0x30000, 0x3fffd, 0x40000, 0x4fffd, 0x50000, 0x5fffd, 0x60000, 0x6fffd, 0x70000, 0x7fffd, 0x80000, 0x8fffd, 0x90000, 0x9fffd, 0xa0000, 0xafffd, 0xb0000, 0xbfffd, 0xc0000, 0xcfffd, 0xd0000, 0xdfffd, 0xe0000, 0xe0000, 0xe0002, 0xe001f, 0xe0080, 0xefffd];
440// prettier-ignore-end
441
442const isUnassignedCodePoint = character => inRange(character, unassigned_code_points);
443
444// prettier-ignore-start
445/**
446 * B.1 Commonly mapped to nothing
447 * @link https://tools.ietf.org/html/rfc3454#appendix-B.1
448 */
449const commonly_mapped_to_nothing = [0x00ad, 0x00ad, 0x034f, 0x034f, 0x1806, 0x1806, 0x180b, 0x180b, 0x180c, 0x180c, 0x180d, 0x180d, 0x200b, 0x200b, 0x200c, 0x200c, 0x200d, 0x200d, 0x2060, 0x2060, 0xfe00, 0xfe00, 0xfe01, 0xfe01, 0xfe02, 0xfe02, 0xfe03, 0xfe03, 0xfe04, 0xfe04, 0xfe05, 0xfe05, 0xfe06, 0xfe06, 0xfe07, 0xfe07, 0xfe08, 0xfe08, 0xfe09, 0xfe09, 0xfe0a, 0xfe0a, 0xfe0b, 0xfe0b, 0xfe0c, 0xfe0c, 0xfe0d, 0xfe0d, 0xfe0e, 0xfe0e, 0xfe0f, 0xfe0f, 0xfeff, 0xfeff];
450// prettier-ignore-end
451
452const isCommonlyMappedToNothing = character => inRange(character, commonly_mapped_to_nothing);
453
454// prettier-ignore-start
455/**
456 * C.1.2 Non-ASCII space characters
457 * @link https://tools.ietf.org/html/rfc3454#appendix-C.1.2
458 */
459const non_ASCII_space_characters = [0x00a0, 0x00a0 /* NO-BREAK SPACE */, 0x1680, 0x1680 /* OGHAM SPACE MARK */, 0x2000, 0x2000 /* EN QUAD */, 0x2001, 0x2001 /* EM QUAD */, 0x2002, 0x2002 /* EN SPACE */, 0x2003, 0x2003 /* EM SPACE */, 0x2004, 0x2004 /* THREE-PER-EM SPACE */, 0x2005, 0x2005 /* FOUR-PER-EM SPACE */, 0x2006, 0x2006 /* SIX-PER-EM SPACE */, 0x2007, 0x2007 /* FIGURE SPACE */, 0x2008, 0x2008 /* PUNCTUATION SPACE */, 0x2009, 0x2009 /* THIN SPACE */, 0x200a, 0x200a /* HAIR SPACE */, 0x200b, 0x200b /* ZERO WIDTH SPACE */, 0x202f, 0x202f /* NARROW NO-BREAK SPACE */, 0x205f, 0x205f /* MEDIUM MATHEMATICAL SPACE */, 0x3000, 0x3000 /* IDEOGRAPHIC SPACE */];
460// prettier-ignore-end
461
462const isNonASCIISpaceCharacter = character => inRange(character, non_ASCII_space_characters);
463
464// prettier-ignore-start
465const non_ASCII_controls_characters = [
466/**
467 * C.2.2 Non-ASCII control characters
468 * @link https://tools.ietf.org/html/rfc3454#appendix-C.2.2
469 */
4700x0080, 0x009f /* [CONTROL CHARACTERS] */, 0x06dd, 0x06dd /* ARABIC END OF AYAH */, 0x070f, 0x070f /* SYRIAC ABBREVIATION MARK */, 0x180e, 0x180e /* MONGOLIAN VOWEL SEPARATOR */, 0x200c, 0x200c /* ZERO WIDTH NON-JOINER */, 0x200d, 0x200d /* ZERO WIDTH JOINER */, 0x2028, 0x2028 /* LINE SEPARATOR */, 0x2029, 0x2029 /* PARAGRAPH SEPARATOR */, 0x2060, 0x2060 /* WORD JOINER */, 0x2061, 0x2061 /* FUNCTION APPLICATION */, 0x2062, 0x2062 /* INVISIBLE TIMES */, 0x2063, 0x2063 /* INVISIBLE SEPARATOR */, 0x206a, 0x206f /* [CONTROL CHARACTERS] */, 0xfeff, 0xfeff /* ZERO WIDTH NO-BREAK SPACE */, 0xfff9, 0xfffc /* [CONTROL CHARACTERS] */, 0x1d173, 0x1d17a /* [MUSICAL CONTROL CHARACTERS] */];
471const non_character_codepoints = [
472/**
473 * C.4 Non-character code points
474 * @link https://tools.ietf.org/html/rfc3454#appendix-C.4
475 */
4760xfdd0, 0xfdef /* [NONCHARACTER CODE POINTS] */, 0xfffe, 0xffff /* [NONCHARACTER CODE POINTS] */, 0x1fffe, 0x1ffff /* [NONCHARACTER CODE POINTS] */, 0x2fffe, 0x2ffff /* [NONCHARACTER CODE POINTS] */, 0x3fffe, 0x3ffff /* [NONCHARACTER CODE POINTS] */, 0x4fffe, 0x4ffff /* [NONCHARACTER CODE POINTS] */, 0x5fffe, 0x5ffff /* [NONCHARACTER CODE POINTS] */, 0x6fffe, 0x6ffff /* [NONCHARACTER CODE POINTS] */, 0x7fffe, 0x7ffff /* [NONCHARACTER CODE POINTS] */, 0x8fffe, 0x8ffff /* [NONCHARACTER CODE POINTS] */, 0x9fffe, 0x9ffff /* [NONCHARACTER CODE POINTS] */, 0xafffe, 0xaffff /* [NONCHARACTER CODE POINTS] */, 0xbfffe, 0xbffff /* [NONCHARACTER CODE POINTS] */, 0xcfffe, 0xcffff /* [NONCHARACTER CODE POINTS] */, 0xdfffe, 0xdffff /* [NONCHARACTER CODE POINTS] */, 0xefffe, 0xeffff /* [NONCHARACTER CODE POINTS] */, 0x10fffe, 0x10ffff /* [NONCHARACTER CODE POINTS] */];
477
478/**
479 * 2.3. Prohibited Output
480 */
481const prohibited_characters = [
482/**
483 * C.2.1 ASCII control characters
484 * @link https://tools.ietf.org/html/rfc3454#appendix-C.2.1
485 */
4860, 0x001f /* [CONTROL CHARACTERS] */, 0x007f, 0x007f /* DELETE */,
487/**
488 * C.8 Change display properties or are deprecated
489 * @link https://tools.ietf.org/html/rfc3454#appendix-C.8
490 */
4910x0340, 0x0340 /* COMBINING GRAVE TONE MARK */, 0x0341, 0x0341 /* COMBINING ACUTE TONE MARK */, 0x200e, 0x200e /* LEFT-TO-RIGHT MARK */, 0x200f, 0x200f /* RIGHT-TO-LEFT MARK */, 0x202a, 0x202a /* LEFT-TO-RIGHT EMBEDDING */, 0x202b, 0x202b /* RIGHT-TO-LEFT EMBEDDING */, 0x202c, 0x202c /* POP DIRECTIONAL FORMATTING */, 0x202d, 0x202d /* LEFT-TO-RIGHT OVERRIDE */, 0x202e, 0x202e /* RIGHT-TO-LEFT OVERRIDE */, 0x206a, 0x206a /* INHIBIT SYMMETRIC SWAPPING */, 0x206b, 0x206b /* ACTIVATE SYMMETRIC SWAPPING */, 0x206c, 0x206c /* INHIBIT ARABIC FORM SHAPING */, 0x206d, 0x206d /* ACTIVATE ARABIC FORM SHAPING */, 0x206e, 0x206e /* NATIONAL DIGIT SHAPES */, 0x206f, 0x206f /* NOMINAL DIGIT SHAPES */,
492/**
493 * C.7 Inappropriate for canonical representation
494 * @link https://tools.ietf.org/html/rfc3454#appendix-C.7
495 */
4960x2ff0, 0x2ffb /* [IDEOGRAPHIC DESCRIPTION CHARACTERS] */,
497/**
498 * C.5 Surrogate codes
499 * @link https://tools.ietf.org/html/rfc3454#appendix-C.5
500 */
5010xd800, 0xdfff,
502/**
503 * C.3 Private use
504 * @link https://tools.ietf.org/html/rfc3454#appendix-C.3
505 */
5060xe000, 0xf8ff /* [PRIVATE USE, PLANE 0] */,
507/**
508 * C.6 Inappropriate for plain text
509 * @link https://tools.ietf.org/html/rfc3454#appendix-C.6
510 */
5110xfff9, 0xfff9 /* INTERLINEAR ANNOTATION ANCHOR */, 0xfffa, 0xfffa /* INTERLINEAR ANNOTATION SEPARATOR */, 0xfffb, 0xfffb /* INTERLINEAR ANNOTATION TERMINATOR */, 0xfffc, 0xfffc /* OBJECT REPLACEMENT CHARACTER */, 0xfffd, 0xfffd /* REPLACEMENT CHARACTER */,
512/**
513 * C.9 Tagging characters
514 * @link https://tools.ietf.org/html/rfc3454#appendix-C.9
515 */
5160xe0001, 0xe0001 /* LANGUAGE TAG */, 0xe0020, 0xe007f /* [TAGGING CHARACTERS] */,
517/**
518 * C.3 Private use
519 * @link https://tools.ietf.org/html/rfc3454#appendix-C.3
520 */
521
5220xf0000, 0xffffd /* [PRIVATE USE, PLANE 15] */, 0x100000, 0x10fffd /* [PRIVATE USE, PLANE 16] */];
523// prettier-ignore-end
524
525const isProhibitedCharacter = character => inRange(character, non_ASCII_space_characters) || inRange(character, prohibited_characters) || inRange(character, non_ASCII_controls_characters) || inRange(character, non_character_codepoints);
526
527// prettier-ignore-start
528/**
529 * D.1 Characters with bidirectional property "R" or "AL"
530 * @link https://tools.ietf.org/html/rfc3454#appendix-D.1
531 */
532const bidirectional_r_al = [0x05be, 0x05be, 0x05c0, 0x05c0, 0x05c3, 0x05c3, 0x05d0, 0x05ea, 0x05f0, 0x05f4, 0x061b, 0x061b, 0x061f, 0x061f, 0x0621, 0x063a, 0x0640, 0x064a, 0x066d, 0x066f, 0x0671, 0x06d5, 0x06dd, 0x06dd, 0x06e5, 0x06e6, 0x06fa, 0x06fe, 0x0700, 0x070d, 0x0710, 0x0710, 0x0712, 0x072c, 0x0780, 0x07a5, 0x07b1, 0x07b1, 0x200f, 0x200f, 0xfb1d, 0xfb1d, 0xfb1f, 0xfb28, 0xfb2a, 0xfb36, 0xfb38, 0xfb3c, 0xfb3e, 0xfb3e, 0xfb40, 0xfb41, 0xfb43, 0xfb44, 0xfb46, 0xfbb1, 0xfbd3, 0xfd3d, 0xfd50, 0xfd8f, 0xfd92, 0xfdc7, 0xfdf0, 0xfdfc, 0xfe70, 0xfe74, 0xfe76, 0xfefc];
533// prettier-ignore-end
534
535const isBidirectionalRAL = character => inRange(character, bidirectional_r_al);
536
537// prettier-ignore-start
538/**
539 * D.2 Characters with bidirectional property "L"
540 * @link https://tools.ietf.org/html/rfc3454#appendix-D.2
541 */
542const bidirectional_l = [0x0041, 0x005a, 0x0061, 0x007a, 0x00aa, 0x00aa, 0x00b5, 0x00b5, 0x00ba, 0x00ba, 0x00c0, 0x00d6, 0x00d8, 0x00f6, 0x00f8, 0x0220, 0x0222, 0x0233, 0x0250, 0x02ad, 0x02b0, 0x02b8, 0x02bb, 0x02c1, 0x02d0, 0x02d1, 0x02e0, 0x02e4, 0x02ee, 0x02ee, 0x037a, 0x037a, 0x0386, 0x0386, 0x0388, 0x038a, 0x038c, 0x038c, 0x038e, 0x03a1, 0x03a3, 0x03ce, 0x03d0, 0x03f5, 0x0400, 0x0482, 0x048a, 0x04ce, 0x04d0, 0x04f5, 0x04f8, 0x04f9, 0x0500, 0x050f, 0x0531, 0x0556, 0x0559, 0x055f, 0x0561, 0x0587, 0x0589, 0x0589, 0x0903, 0x0903, 0x0905, 0x0939, 0x093d, 0x0940, 0x0949, 0x094c, 0x0950, 0x0950, 0x0958, 0x0961, 0x0964, 0x0970, 0x0982, 0x0983, 0x0985, 0x098c, 0x098f, 0x0990, 0x0993, 0x09a8, 0x09aa, 0x09b0, 0x09b2, 0x09b2, 0x09b6, 0x09b9, 0x09be, 0x09c0, 0x09c7, 0x09c8, 0x09cb, 0x09cc, 0x09d7, 0x09d7, 0x09dc, 0x09dd, 0x09df, 0x09e1, 0x09e6, 0x09f1, 0x09f4, 0x09fa, 0x0a05, 0x0a0a, 0x0a0f, 0x0a10, 0x0a13, 0x0a28, 0x0a2a, 0x0a30, 0x0a32, 0x0a33, 0x0a35, 0x0a36, 0x0a38, 0x0a39, 0x0a3e, 0x0a40, 0x0a59, 0x0a5c, 0x0a5e, 0x0a5e, 0x0a66, 0x0a6f, 0x0a72, 0x0a74, 0x0a83, 0x0a83, 0x0a85, 0x0a8b, 0x0a8d, 0x0a8d, 0x0a8f, 0x0a91, 0x0a93, 0x0aa8, 0x0aaa, 0x0ab0, 0x0ab2, 0x0ab3, 0x0ab5, 0x0ab9, 0x0abd, 0x0ac0, 0x0ac9, 0x0ac9, 0x0acb, 0x0acc, 0x0ad0, 0x0ad0, 0x0ae0, 0x0ae0, 0x0ae6, 0x0aef, 0x0b02, 0x0b03, 0x0b05, 0x0b0c, 0x0b0f, 0x0b10, 0x0b13, 0x0b28, 0x0b2a, 0x0b30, 0x0b32, 0x0b33, 0x0b36, 0x0b39, 0x0b3d, 0x0b3e, 0x0b40, 0x0b40, 0x0b47, 0x0b48, 0x0b4b, 0x0b4c, 0x0b57, 0x0b57, 0x0b5c, 0x0b5d, 0x0b5f, 0x0b61, 0x0b66, 0x0b70, 0x0b83, 0x0b83, 0x0b85, 0x0b8a, 0x0b8e, 0x0b90, 0x0b92, 0x0b95, 0x0b99, 0x0b9a, 0x0b9c, 0x0b9c, 0x0b9e, 0x0b9f, 0x0ba3, 0x0ba4, 0x0ba8, 0x0baa, 0x0bae, 0x0bb5, 0x0bb7, 0x0bb9, 0x0bbe, 0x0bbf, 0x0bc1, 0x0bc2, 0x0bc6, 0x0bc8, 0x0bca, 0x0bcc, 0x0bd7, 0x0bd7, 0x0be7, 0x0bf2, 0x0c01, 0x0c03, 0x0c05, 0x0c0c, 0x0c0e, 0x0c10, 0x0c12, 0x0c28, 0x0c2a, 0x0c33, 0x0c35, 0x0c39, 0x0c41, 0x0c44, 0x0c60, 0x0c61, 0x0c66, 0x0c6f, 0x0c82, 0x0c83, 0x0c85, 0x0c8c, 0x0c8e, 0x0c90, 0x0c92, 0x0ca8, 0x0caa, 0x0cb3, 0x0cb5, 0x0cb9, 0x0cbe, 0x0cbe, 0x0cc0, 0x0cc4, 0x0cc7, 0x0cc8, 0x0cca, 0x0ccb, 0x0cd5, 0x0cd6, 0x0cde, 0x0cde, 0x0ce0, 0x0ce1, 0x0ce6, 0x0cef, 0x0d02, 0x0d03, 0x0d05, 0x0d0c, 0x0d0e, 0x0d10, 0x0d12, 0x0d28, 0x0d2a, 0x0d39, 0x0d3e, 0x0d40, 0x0d46, 0x0d48, 0x0d4a, 0x0d4c, 0x0d57, 0x0d57, 0x0d60, 0x0d61, 0x0d66, 0x0d6f, 0x0d82, 0x0d83, 0x0d85, 0x0d96, 0x0d9a, 0x0db1, 0x0db3, 0x0dbb, 0x0dbd, 0x0dbd, 0x0dc0, 0x0dc6, 0x0dcf, 0x0dd1, 0x0dd8, 0x0ddf, 0x0df2, 0x0df4, 0x0e01, 0x0e30, 0x0e32, 0x0e33, 0x0e40, 0x0e46, 0x0e4f, 0x0e5b, 0x0e81, 0x0e82, 0x0e84, 0x0e84, 0x0e87, 0x0e88, 0x0e8a, 0x0e8a, 0x0e8d, 0x0e8d, 0x0e94, 0x0e97, 0x0e99, 0x0e9f, 0x0ea1, 0x0ea3, 0x0ea5, 0x0ea5, 0x0ea7, 0x0ea7, 0x0eaa, 0x0eab, 0x0ead, 0x0eb0, 0x0eb2, 0x0eb3, 0x0ebd, 0x0ebd, 0x0ec0, 0x0ec4, 0x0ec6, 0x0ec6, 0x0ed0, 0x0ed9, 0x0edc, 0x0edd, 0x0f00, 0x0f17, 0x0f1a, 0x0f34, 0x0f36, 0x0f36, 0x0f38, 0x0f38, 0x0f3e, 0x0f47, 0x0f49, 0x0f6a, 0x0f7f, 0x0f7f, 0x0f85, 0x0f85, 0x0f88, 0x0f8b, 0x0fbe, 0x0fc5, 0x0fc7, 0x0fcc, 0x0fcf, 0x0fcf, 0x1000, 0x1021, 0x1023, 0x1027, 0x1029, 0x102a, 0x102c, 0x102c, 0x1031, 0x1031, 0x1038, 0x1038, 0x1040, 0x1057, 0x10a0, 0x10c5, 0x10d0, 0x10f8, 0x10fb, 0x10fb, 0x1100, 0x1159, 0x115f, 0x11a2, 0x11a8, 0x11f9, 0x1200, 0x1206, 0x1208, 0x1246, 0x1248, 0x1248, 0x124a, 0x124d, 0x1250, 0x1256, 0x1258, 0x1258, 0x125a, 0x125d, 0x1260, 0x1286, 0x1288, 0x1288, 0x128a, 0x128d, 0x1290, 0x12ae, 0x12b0, 0x12b0, 0x12b2, 0x12b5, 0x12b8, 0x12be, 0x12c0, 0x12c0, 0x12c2, 0x12c5, 0x12c8, 0x12ce, 0x12d0, 0x12d6, 0x12d8, 0x12ee, 0x12f0, 0x130e, 0x1310, 0x1310, 0x1312, 0x1315, 0x1318, 0x131e, 0x1320, 0x1346, 0x1348, 0x135a, 0x1361, 0x137c, 0x13a0, 0x13f4, 0x1401, 0x1676, 0x1681, 0x169a, 0x16a0, 0x16f0, 0x1700, 0x170c, 0x170e, 0x1711, 0x1720, 0x1731, 0x1735, 0x1736, 0x1740, 0x1751, 0x1760, 0x176c, 0x176e, 0x1770, 0x1780, 0x17b6, 0x17be, 0x17c5, 0x17c7, 0x17c8, 0x17d4, 0x17da, 0x17dc, 0x17dc, 0x17e0, 0x17e9, 0x1810, 0x1819, 0x1820, 0x1877, 0x1880, 0x18a8, 0x1e00, 0x1e9b, 0x1ea0, 0x1ef9, 0x1f00, 0x1f15, 0x1f18, 0x1f1d, 0x1f20, 0x1f45, 0x1f48, 0x1f4d, 0x1f50, 0x1f57, 0x1f59, 0x1f59, 0x1f5b, 0x1f5b, 0x1f5d, 0x1f5d, 0x1f5f, 0x1f7d, 0x1f80, 0x1fb4, 0x1fb6, 0x1fbc, 0x1fbe, 0x1fbe, 0x1fc2, 0x1fc4, 0x1fc6, 0x1fcc, 0x1fd0, 0x1fd3, 0x1fd6, 0x1fdb, 0x1fe0, 0x1fec, 0x1ff2, 0x1ff4, 0x1ff6, 0x1ffc, 0x200e, 0x200e, 0x2071, 0x2071, 0x207f, 0x207f, 0x2102, 0x2102, 0x2107, 0x2107, 0x210a, 0x2113, 0x2115, 0x2115, 0x2119, 0x211d, 0x2124, 0x2124, 0x2126, 0x2126, 0x2128, 0x2128, 0x212a, 0x212d, 0x212f, 0x2131, 0x2133, 0x2139, 0x213d, 0x213f, 0x2145, 0x2149, 0x2160, 0x2183, 0x2336, 0x237a, 0x2395, 0x2395, 0x249c, 0x24e9, 0x3005, 0x3007, 0x3021, 0x3029, 0x3031, 0x3035, 0x3038, 0x303c, 0x3041, 0x3096, 0x309d, 0x309f, 0x30a1, 0x30fa, 0x30fc, 0x30ff, 0x3105, 0x312c, 0x3131, 0x318e, 0x3190, 0x31b7, 0x31f0, 0x321c, 0x3220, 0x3243, 0x3260, 0x327b, 0x327f, 0x32b0, 0x32c0, 0x32cb, 0x32d0, 0x32fe, 0x3300, 0x3376, 0x337b, 0x33dd, 0x33e0, 0x33fe, 0x3400, 0x4db5, 0x4e00, 0x9fa5, 0xa000, 0xa48c, 0xac00, 0xd7a3, 0xd800, 0xfa2d, 0xfa30, 0xfa6a, 0xfb00, 0xfb06, 0xfb13, 0xfb17, 0xff21, 0xff3a, 0xff41, 0xff5a, 0xff66, 0xffbe, 0xffc2, 0xffc7, 0xffca, 0xffcf, 0xffd2, 0xffd7, 0xffda, 0xffdc, 0x10300, 0x1031e, 0x10320, 0x10323, 0x10330, 0x1034a, 0x10400, 0x10425, 0x10428, 0x1044d, 0x1d000, 0x1d0f5, 0x1d100, 0x1d126, 0x1d12a, 0x1d166, 0x1d16a, 0x1d172, 0x1d183, 0x1d184, 0x1d18c, 0x1d1a9, 0x1d1ae, 0x1d1dd, 0x1d400, 0x1d454, 0x1d456, 0x1d49c, 0x1d49e, 0x1d49f, 0x1d4a2, 0x1d4a2, 0x1d4a5, 0x1d4a6, 0x1d4a9, 0x1d4ac, 0x1d4ae, 0x1d4b9, 0x1d4bb, 0x1d4bb, 0x1d4bd, 0x1d4c0, 0x1d4c2, 0x1d4c3, 0x1d4c5, 0x1d505, 0x1d507, 0x1d50a, 0x1d50d, 0x1d514, 0x1d516, 0x1d51c, 0x1d51e, 0x1d539, 0x1d53b, 0x1d53e, 0x1d540, 0x1d544, 0x1d546, 0x1d546, 0x1d54a, 0x1d550, 0x1d552, 0x1d6a3, 0x1d6a8, 0x1d7c9, 0x20000, 0x2a6d6, 0x2f800, 0x2fa1d, 0xf0000, 0xffffd, 0x100000, 0x10fffd];
543// prettier-ignore-end
544
545const isBidirectionalL = character => inRange(character, bidirectional_l);
546
547// 2.1. Mapping
548
549/**
550 * non-ASCII space characters [StringPrep, C.1.2] that can be
551 * mapped to SPACE (U+0020)
552 */
553const mapping2space = isNonASCIISpaceCharacter;
554
555/**
556 * the "commonly mapped to nothing" characters [StringPrep, B.1]
557 * that can be mapped to nothing.
558 */
559const mapping2nothing = isCommonlyMappedToNothing;
560
561// utils
562const getCodePoint = character => character.codePointAt(0);
563const first = x => x[0];
564const last = x => x[x.length - 1];
565
566/**
567 * Convert provided string into an array of Unicode Code Points.
568 * Based on https://stackoverflow.com/a/21409165/1556249
569 * and https://www.npmjs.com/package/code-point-at.
570 * @param {string} input
571 * @returns {number[]}
572 */
573function toCodePoints(input) {
574 const codepoints = [];
575 const size = input.length;
576 for (let i = 0; i < size; i += 1) {
577 const before = input.charCodeAt(i);
578 if (before >= 0xd800 && before <= 0xdbff && size > i + 1) {
579 const next = input.charCodeAt(i + 1);
580 if (next >= 0xdc00 && next <= 0xdfff) {
581 codepoints.push((before - 0xd800) * 0x400 + next - 0xdc00 + 0x10000);
582 i += 1;
583 continue;
584 }
585 }
586 codepoints.push(before);
587 }
588 return codepoints;
589}
590
591/**
592 * SASLprep.
593 * @param {string} input
594 * @param {Object} opts
595 * @param {boolean} opts.allowUnassigned
596 * @returns {string}
597 */
598function saslprep(input, opts = {}) {
599 if (typeof input !== 'string') {
600 throw new TypeError('Expected string.');
601 }
602 if (input.length === 0) {
603 return '';
604 }
605
606 // 1. Map
607 const mapped_input = toCodePoints(input)
608 // 1.1 mapping to space
609 .map(character => mapping2space(character) ? 0x20 : character)
610 // 1.2 mapping to nothing
611 .filter(character => !mapping2nothing(character));
612
613 // 2. Normalize
614 const normalized_input = String.fromCodePoint.apply(null, mapped_input).normalize('NFKC');
615 const normalized_map = toCodePoints(normalized_input);
616
617 // 3. Prohibit
618 const hasProhibited = normalized_map.some(isProhibitedCharacter);
619 if (hasProhibited) {
620 throw new Error('Prohibited character, see https://tools.ietf.org/html/rfc4013#section-2.3');
621 }
622
623 // Unassigned Code Points
624 if (opts.allowUnassigned !== true) {
625 const hasUnassigned = normalized_map.some(isUnassignedCodePoint);
626 if (hasUnassigned) {
627 throw new Error('Unassigned code point, see https://tools.ietf.org/html/rfc4013#section-2.5');
628 }
629 }
630
631 // 4. check bidi
632
633 const hasBidiRAL = normalized_map.some(isBidirectionalRAL);
634 const hasBidiL = normalized_map.some(isBidirectionalL);
635
636 // 4.1 If a string contains any RandALCat character, the string MUST NOT
637 // contain any LCat character.
638 if (hasBidiRAL && hasBidiL) {
639 throw new Error('String must not contain RandALCat and LCat at the same time,' + ' see https://tools.ietf.org/html/rfc3454#section-6');
640 }
641
642 /**
643 * 4.2 If a string contains any RandALCat character, a RandALCat
644 * character MUST be the first character of the string, and a
645 * RandALCat character MUST be the last character of the string.
646 */
647
648 const isFirstBidiRAL = isBidirectionalRAL(getCodePoint(first(normalized_input)));
649 const isLastBidiRAL = isBidirectionalRAL(getCodePoint(last(normalized_input)));
650 if (hasBidiRAL && !(isFirstBidiRAL && isLastBidiRAL)) {
651 throw new Error('Bidirectional RandALCat character must be the first and the last' + ' character of the string, see https://tools.ietf.org/html/rfc3454#section-6');
652 }
653 return normalized_input;
654}
655
656/*
657 PDFSecurity - represents PDF security settings
658 By Yang Liu <hi@zesik.com>
659 */
660class PDFSecurity {
661 static generateFileID(info = {}) {
662 let infoStr = `${info.CreationDate.getTime()}\n`;
663 for (let key in info) {
664 // eslint-disable-next-line no-prototype-builtins
665 if (!info.hasOwnProperty(key)) {
666 continue;
667 }
668 infoStr += `${key}: ${info[key].valueOf()}\n`;
669 }
670 return wordArrayToBuffer(CryptoJS.MD5(infoStr));
671 }
672 static generateRandomWordArray(bytes) {
673 return CryptoJS.lib.WordArray.random(bytes);
674 }
675 static create(document, options = {}) {
676 if (!options.ownerPassword && !options.userPassword) {
677 return null;
678 }
679 return new PDFSecurity(document, options);
680 }
681 constructor(document, options = {}) {
682 if (!options.ownerPassword && !options.userPassword) {
683 throw new Error('None of owner password and user password is defined.');
684 }
685 this.document = document;
686 this._setupEncryption(options);
687 }
688 _setupEncryption(options) {
689 switch (options.pdfVersion) {
690 case '1.4':
691 case '1.5':
692 this.version = 2;
693 break;
694 case '1.6':
695 case '1.7':
696 this.version = 4;
697 break;
698 case '1.7ext3':
699 this.version = 5;
700 break;
701 default:
702 this.version = 1;
703 break;
704 }
705 const encDict = {
706 Filter: 'Standard'
707 };
708 switch (this.version) {
709 case 1:
710 case 2:
711 case 4:
712 this._setupEncryptionV1V2V4(this.version, encDict, options);
713 break;
714 case 5:
715 this._setupEncryptionV5(encDict, options);
716 break;
717 }
718 this.dictionary = this.document.ref(encDict);
719 }
720 _setupEncryptionV1V2V4(v, encDict, options) {
721 let r, permissions;
722 switch (v) {
723 case 1:
724 r = 2;
725 this.keyBits = 40;
726 permissions = getPermissionsR2(options.permissions);
727 break;
728 case 2:
729 r = 3;
730 this.keyBits = 128;
731 permissions = getPermissionsR3(options.permissions);
732 break;
733 case 4:
734 r = 4;
735 this.keyBits = 128;
736 permissions = getPermissionsR3(options.permissions);
737 break;
738 }
739 const paddedUserPassword = processPasswordR2R3R4(options.userPassword);
740 const paddedOwnerPassword = options.ownerPassword ? processPasswordR2R3R4(options.ownerPassword) : paddedUserPassword;
741 const ownerPasswordEntry = getOwnerPasswordR2R3R4(r, this.keyBits, paddedUserPassword, paddedOwnerPassword);
742 this.encryptionKey = getEncryptionKeyR2R3R4(r, this.keyBits, this.document._id, paddedUserPassword, ownerPasswordEntry, permissions);
743 let userPasswordEntry;
744 if (r === 2) {
745 userPasswordEntry = getUserPasswordR2(this.encryptionKey);
746 } else {
747 userPasswordEntry = getUserPasswordR3R4(this.document._id, this.encryptionKey);
748 }
749 encDict.V = v;
750 if (v >= 2) {
751 encDict.Length = this.keyBits;
752 }
753 if (v === 4) {
754 encDict.CF = {
755 StdCF: {
756 AuthEvent: 'DocOpen',
757 CFM: 'AESV2',
758 Length: this.keyBits / 8
759 }
760 };
761 encDict.StmF = 'StdCF';
762 encDict.StrF = 'StdCF';
763 }
764 encDict.R = r;
765 encDict.O = wordArrayToBuffer(ownerPasswordEntry);
766 encDict.U = wordArrayToBuffer(userPasswordEntry);
767 encDict.P = permissions;
768 }
769 _setupEncryptionV5(encDict, options) {
770 this.keyBits = 256;
771 const permissions = getPermissionsR3(options.permissions);
772 const processedUserPassword = processPasswordR5(options.userPassword);
773 const processedOwnerPassword = options.ownerPassword ? processPasswordR5(options.ownerPassword) : processedUserPassword;
774 this.encryptionKey = getEncryptionKeyR5(PDFSecurity.generateRandomWordArray);
775 const userPasswordEntry = getUserPasswordR5(processedUserPassword, PDFSecurity.generateRandomWordArray);
776 const userKeySalt = CryptoJS.lib.WordArray.create(userPasswordEntry.words.slice(10, 12), 8);
777 const userEncryptionKeyEntry = getUserEncryptionKeyR5(processedUserPassword, userKeySalt, this.encryptionKey);
778 const ownerPasswordEntry = getOwnerPasswordR5(processedOwnerPassword, userPasswordEntry, PDFSecurity.generateRandomWordArray);
779 const ownerKeySalt = CryptoJS.lib.WordArray.create(ownerPasswordEntry.words.slice(10, 12), 8);
780 const ownerEncryptionKeyEntry = getOwnerEncryptionKeyR5(processedOwnerPassword, ownerKeySalt, userPasswordEntry, this.encryptionKey);
781 const permsEntry = getEncryptedPermissionsR5(permissions, this.encryptionKey, PDFSecurity.generateRandomWordArray);
782 encDict.V = 5;
783 encDict.Length = this.keyBits;
784 encDict.CF = {
785 StdCF: {
786 AuthEvent: 'DocOpen',
787 CFM: 'AESV3',
788 Length: this.keyBits / 8
789 }
790 };
791 encDict.StmF = 'StdCF';
792 encDict.StrF = 'StdCF';
793 encDict.R = 5;
794 encDict.O = wordArrayToBuffer(ownerPasswordEntry);
795 encDict.OE = wordArrayToBuffer(ownerEncryptionKeyEntry);
796 encDict.U = wordArrayToBuffer(userPasswordEntry);
797 encDict.UE = wordArrayToBuffer(userEncryptionKeyEntry);
798 encDict.P = permissions;
799 encDict.Perms = wordArrayToBuffer(permsEntry);
800 }
801 getEncryptFn(obj, gen) {
802 let digest;
803 if (this.version < 5) {
804 digest = this.encryptionKey.clone().concat(CryptoJS.lib.WordArray.create([(obj & 0xff) << 24 | (obj & 0xff00) << 8 | obj >> 8 & 0xff00 | gen & 0xff, (gen & 0xff00) << 16], 5));
805 }
806 if (this.version === 1 || this.version === 2) {
807 let key = CryptoJS.MD5(digest);
808 key.sigBytes = Math.min(16, this.keyBits / 8 + 5);
809 return buffer => wordArrayToBuffer(CryptoJS.RC4.encrypt(CryptoJS.lib.WordArray.create(buffer), key).ciphertext);
810 }
811 let key;
812 if (this.version === 4) {
813 key = CryptoJS.MD5(digest.concat(CryptoJS.lib.WordArray.create([0x73416c54], 4)));
814 } else {
815 key = this.encryptionKey;
816 }
817 const iv = PDFSecurity.generateRandomWordArray(16);
818 const options = {
819 mode: CryptoJS.mode.CBC,
820 padding: CryptoJS.pad.Pkcs7,
821 iv
822 };
823 return buffer => wordArrayToBuffer(iv.clone().concat(CryptoJS.AES.encrypt(CryptoJS.lib.WordArray.create(buffer), key, options).ciphertext));
824 }
825 end() {
826 this.dictionary.end();
827 }
828}
829function getPermissionsR2(permissionObject = {}) {
830 let permissions = 0xffffffc0 >> 0;
831 if (permissionObject.printing) {
832 permissions |= 0b000000000100;
833 }
834 if (permissionObject.modifying) {
835 permissions |= 0b000000001000;
836 }
837 if (permissionObject.copying) {
838 permissions |= 0b000000010000;
839 }
840 if (permissionObject.annotating) {
841 permissions |= 0b000000100000;
842 }
843 return permissions;
844}
845function getPermissionsR3(permissionObject = {}) {
846 let permissions = 0xfffff0c0 >> 0;
847 if (permissionObject.printing === 'lowResolution') {
848 permissions |= 0b000000000100;
849 }
850 if (permissionObject.printing === 'highResolution') {
851 permissions |= 0b100000000100;
852 }
853 if (permissionObject.modifying) {
854 permissions |= 0b000000001000;
855 }
856 if (permissionObject.copying) {
857 permissions |= 0b000000010000;
858 }
859 if (permissionObject.annotating) {
860 permissions |= 0b000000100000;
861 }
862 if (permissionObject.fillingForms) {
863 permissions |= 0b000100000000;
864 }
865 if (permissionObject.contentAccessibility) {
866 permissions |= 0b001000000000;
867 }
868 if (permissionObject.documentAssembly) {
869 permissions |= 0b010000000000;
870 }
871 return permissions;
872}
873function getUserPasswordR2(encryptionKey) {
874 return CryptoJS.RC4.encrypt(processPasswordR2R3R4(), encryptionKey).ciphertext;
875}
876function getUserPasswordR3R4(documentId, encryptionKey) {
877 const key = encryptionKey.clone();
878 let cipher = CryptoJS.MD5(processPasswordR2R3R4().concat(CryptoJS.lib.WordArray.create(documentId)));
879 for (let i = 0; i < 20; i++) {
880 const xorRound = Math.ceil(key.sigBytes / 4);
881 for (let j = 0; j < xorRound; j++) {
882 key.words[j] = encryptionKey.words[j] ^ (i | i << 8 | i << 16 | i << 24);
883 }
884 cipher = CryptoJS.RC4.encrypt(cipher, key).ciphertext;
885 }
886 return cipher.concat(CryptoJS.lib.WordArray.create(null, 16));
887}
888function getOwnerPasswordR2R3R4(r, keyBits, paddedUserPassword, paddedOwnerPassword) {
889 let digest = paddedOwnerPassword;
890 let round = r >= 3 ? 51 : 1;
891 for (let i = 0; i < round; i++) {
892 digest = CryptoJS.MD5(digest);
893 }
894 const key = digest.clone();
895 key.sigBytes = keyBits / 8;
896 let cipher = paddedUserPassword;
897 round = r >= 3 ? 20 : 1;
898 for (let i = 0; i < round; i++) {
899 const xorRound = Math.ceil(key.sigBytes / 4);
900 for (let j = 0; j < xorRound; j++) {
901 key.words[j] = digest.words[j] ^ (i | i << 8 | i << 16 | i << 24);
902 }
903 cipher = CryptoJS.RC4.encrypt(cipher, key).ciphertext;
904 }
905 return cipher;
906}
907function getEncryptionKeyR2R3R4(r, keyBits, documentId, paddedUserPassword, ownerPasswordEntry, permissions) {
908 let key = paddedUserPassword.clone().concat(ownerPasswordEntry).concat(CryptoJS.lib.WordArray.create([lsbFirstWord(permissions)], 4)).concat(CryptoJS.lib.WordArray.create(documentId));
909 const round = r >= 3 ? 51 : 1;
910 for (let i = 0; i < round; i++) {
911 key = CryptoJS.MD5(key);
912 key.sigBytes = keyBits / 8;
913 }
914 return key;
915}
916function getUserPasswordR5(processedUserPassword, generateRandomWordArray) {
917 const validationSalt = generateRandomWordArray(8);
918 const keySalt = generateRandomWordArray(8);
919 return CryptoJS.SHA256(processedUserPassword.clone().concat(validationSalt)).concat(validationSalt).concat(keySalt);
920}
921function getUserEncryptionKeyR5(processedUserPassword, userKeySalt, encryptionKey) {
922 const key = CryptoJS.SHA256(processedUserPassword.clone().concat(userKeySalt));
923 const options = {
924 mode: CryptoJS.mode.CBC,
925 padding: CryptoJS.pad.NoPadding,
926 iv: CryptoJS.lib.WordArray.create(null, 16)
927 };
928 return CryptoJS.AES.encrypt(encryptionKey, key, options).ciphertext;
929}
930function getOwnerPasswordR5(processedOwnerPassword, userPasswordEntry, generateRandomWordArray) {
931 const validationSalt = generateRandomWordArray(8);
932 const keySalt = generateRandomWordArray(8);
933 return CryptoJS.SHA256(processedOwnerPassword.clone().concat(validationSalt).concat(userPasswordEntry)).concat(validationSalt).concat(keySalt);
934}
935function getOwnerEncryptionKeyR5(processedOwnerPassword, ownerKeySalt, userPasswordEntry, encryptionKey) {
936 const key = CryptoJS.SHA256(processedOwnerPassword.clone().concat(ownerKeySalt).concat(userPasswordEntry));
937 const options = {
938 mode: CryptoJS.mode.CBC,
939 padding: CryptoJS.pad.NoPadding,
940 iv: CryptoJS.lib.WordArray.create(null, 16)
941 };
942 return CryptoJS.AES.encrypt(encryptionKey, key, options).ciphertext;
943}
944function getEncryptionKeyR5(generateRandomWordArray) {
945 return generateRandomWordArray(32);
946}
947function getEncryptedPermissionsR5(permissions, encryptionKey, generateRandomWordArray) {
948 const cipher = CryptoJS.lib.WordArray.create([lsbFirstWord(permissions), 0xffffffff, 0x54616462], 12).concat(generateRandomWordArray(4));
949 const options = {
950 mode: CryptoJS.mode.ECB,
951 padding: CryptoJS.pad.NoPadding
952 };
953 return CryptoJS.AES.encrypt(cipher, encryptionKey, options).ciphertext;
954}
955function processPasswordR2R3R4(password = '') {
956 const out = Buffer.alloc(32);
957 const length = password.length;
958 let index = 0;
959 while (index < length && index < 32) {
960 const code = password.charCodeAt(index);
961 if (code > 0xff) {
962 throw new Error('Password contains one or more invalid characters.');
963 }
964 out[index] = code;
965 index++;
966 }
967 while (index < 32) {
968 out[index] = PASSWORD_PADDING[index - length];
969 index++;
970 }
971 return CryptoJS.lib.WordArray.create(out);
972}
973function processPasswordR5(password = '') {
974 password = unescape(encodeURIComponent(saslprep(password)));
975 const length = Math.min(127, password.length);
976 const out = Buffer.alloc(length);
977 for (let i = 0; i < length; i++) {
978 out[i] = password.charCodeAt(i);
979 }
980 return CryptoJS.lib.WordArray.create(out);
981}
982function lsbFirstWord(data) {
983 return (data & 0xff) << 24 | (data & 0xff00) << 8 | data >> 8 & 0xff00 | data >> 24 & 0xff;
984}
985function wordArrayToBuffer(wordArray) {
986 const byteArray = [];
987 for (let i = 0; i < wordArray.sigBytes; i++) {
988 byteArray.push(wordArray.words[Math.floor(i / 4)] >> 8 * (3 - i % 4) & 0xff);
989 }
990 return Buffer.from(byteArray);
991}
992const 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];
993
994const {
995 number
996} = PDFObject;
997class PDFGradient {
998 constructor(doc) {
999 this.doc = doc;
1000 this.stops = [];
1001 this.embedded = false;
1002 this.transform = [1, 0, 0, 1, 0, 0];
1003 }
1004 stop(pos, color, opacity) {
1005 if (opacity == null) {
1006 opacity = 1;
1007 }
1008 color = this.doc._normalizeColor(color);
1009 if (this.stops.length === 0) {
1010 if (color.length === 3) {
1011 this._colorSpace = 'DeviceRGB';
1012 } else if (color.length === 4) {
1013 this._colorSpace = 'DeviceCMYK';
1014 } else if (color.length === 1) {
1015 this._colorSpace = 'DeviceGray';
1016 } else {
1017 throw new Error('Unknown color space');
1018 }
1019 } else if (this._colorSpace === 'DeviceRGB' && color.length !== 3 || this._colorSpace === 'DeviceCMYK' && color.length !== 4 || this._colorSpace === 'DeviceGray' && color.length !== 1) {
1020 throw new Error('All gradient stops must use the same color space');
1021 }
1022 opacity = Math.max(0, Math.min(1, opacity));
1023 this.stops.push([pos, color, opacity]);
1024 return this;
1025 }
1026 setTransform(m11, m12, m21, m22, dx, dy) {
1027 this.transform = [m11, m12, m21, m22, dx, dy];
1028 return this;
1029 }
1030 embed(m) {
1031 let fn;
1032 const stopsLength = this.stops.length;
1033 if (stopsLength === 0) {
1034 return;
1035 }
1036 this.embedded = true;
1037 this.matrix = m;
1038
1039 // if the last stop comes before 100%, add a copy at 100%
1040 const last = this.stops[stopsLength - 1];
1041 if (last[0] < 1) {
1042 this.stops.push([1, last[1], last[2]]);
1043 }
1044 const bounds = [];
1045 const encode = [];
1046 const stops = [];
1047 for (let i = 0; i < stopsLength - 1; i++) {
1048 encode.push(0, 1);
1049 if (i + 2 !== stopsLength) {
1050 bounds.push(this.stops[i + 1][0]);
1051 }
1052 fn = this.doc.ref({
1053 FunctionType: 2,
1054 Domain: [0, 1],
1055 C0: this.stops[i + 0][1],
1056 C1: this.stops[i + 1][1],
1057 N: 1
1058 });
1059 stops.push(fn);
1060 fn.end();
1061 }
1062
1063 // if there are only two stops, we don't need a stitching function
1064 if (stopsLength === 1) {
1065 fn = stops[0];
1066 } else {
1067 fn = this.doc.ref({
1068 FunctionType: 3,
1069 // stitching function
1070 Domain: [0, 1],
1071 Functions: stops,
1072 Bounds: bounds,
1073 Encode: encode
1074 });
1075 fn.end();
1076 }
1077 this.id = `Sh${++this.doc._gradCount}`;
1078 const shader = this.shader(fn);
1079 shader.end();
1080 const pattern = this.doc.ref({
1081 Type: 'Pattern',
1082 PatternType: 2,
1083 Shading: shader,
1084 Matrix: this.matrix.map(number)
1085 });
1086 pattern.end();
1087 if (this.stops.some(stop => stop[2] < 1)) {
1088 let grad = this.opacityGradient();
1089 grad._colorSpace = 'DeviceGray';
1090 for (let stop of this.stops) {
1091 grad.stop(stop[0], [stop[2]]);
1092 }
1093 grad = grad.embed(this.matrix);
1094 const pageBBox = [0, 0, this.doc.page.width, this.doc.page.height];
1095 const form = this.doc.ref({
1096 Type: 'XObject',
1097 Subtype: 'Form',
1098 FormType: 1,
1099 BBox: pageBBox,
1100 Group: {
1101 Type: 'Group',
1102 S: 'Transparency',
1103 CS: 'DeviceGray'
1104 },
1105 Resources: {
1106 ProcSet: ['PDF', 'Text', 'ImageB', 'ImageC', 'ImageI'],
1107 Pattern: {
1108 Sh1: grad
1109 }
1110 }
1111 });
1112 form.write('/Pattern cs /Sh1 scn');
1113 form.end(`${pageBBox.join(' ')} re f`);
1114 const gstate = this.doc.ref({
1115 Type: 'ExtGState',
1116 SMask: {
1117 Type: 'Mask',
1118 S: 'Luminosity',
1119 G: form
1120 }
1121 });
1122 gstate.end();
1123 const opacityPattern = this.doc.ref({
1124 Type: 'Pattern',
1125 PatternType: 1,
1126 PaintType: 1,
1127 TilingType: 2,
1128 BBox: pageBBox,
1129 XStep: pageBBox[2],
1130 YStep: pageBBox[3],
1131 Resources: {
1132 ProcSet: ['PDF', 'Text', 'ImageB', 'ImageC', 'ImageI'],
1133 Pattern: {
1134 Sh1: pattern
1135 },
1136 ExtGState: {
1137 Gs1: gstate
1138 }
1139 }
1140 });
1141 opacityPattern.write('/Gs1 gs /Pattern cs /Sh1 scn');
1142 opacityPattern.end(`${pageBBox.join(' ')} re f`);
1143 this.doc.page.patterns[this.id] = opacityPattern;
1144 } else {
1145 this.doc.page.patterns[this.id] = pattern;
1146 }
1147 return pattern;
1148 }
1149 apply(stroke) {
1150 // apply gradient transform to existing document ctm
1151 const [m0, m1, m2, m3, m4, m5] = this.doc._ctm;
1152 const [m11, m12, m21, m22, dx, dy] = this.transform;
1153 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];
1154 if (!this.embedded || m.join(' ') !== this.matrix.join(' ')) {
1155 this.embed(m);
1156 }
1157 this.doc._setColorSpace('Pattern', stroke);
1158 const op = stroke ? 'SCN' : 'scn';
1159 return this.doc.addContent(`/${this.id} ${op}`);
1160 }
1161}
1162class PDFLinearGradient extends PDFGradient {
1163 constructor(doc, x1, y1, x2, y2) {
1164 super(doc);
1165 this.x1 = x1;
1166 this.y1 = y1;
1167 this.x2 = x2;
1168 this.y2 = y2;
1169 }
1170 shader(fn) {
1171 return this.doc.ref({
1172 ShadingType: 2,
1173 ColorSpace: this._colorSpace,
1174 Coords: [this.x1, this.y1, this.x2, this.y2],
1175 Function: fn,
1176 Extend: [true, true]
1177 });
1178 }
1179 opacityGradient() {
1180 return new PDFLinearGradient(this.doc, this.x1, this.y1, this.x2, this.y2);
1181 }
1182}
1183class PDFRadialGradient extends PDFGradient {
1184 constructor(doc, x1, y1, r1, x2, y2, r2) {
1185 super(doc);
1186 this.doc = doc;
1187 this.x1 = x1;
1188 this.y1 = y1;
1189 this.r1 = r1;
1190 this.x2 = x2;
1191 this.y2 = y2;
1192 this.r2 = r2;
1193 }
1194 shader(fn) {
1195 return this.doc.ref({
1196 ShadingType: 3,
1197 ColorSpace: this._colorSpace,
1198 Coords: [this.x1, this.y1, this.r1, this.x2, this.y2, this.r2],
1199 Function: fn,
1200 Extend: [true, true]
1201 });
1202 }
1203 opacityGradient() {
1204 return new PDFRadialGradient(this.doc, this.x1, this.y1, this.r1, this.x2, this.y2, this.r2);
1205 }
1206}
1207var Gradient = {
1208 PDFGradient,
1209 PDFLinearGradient,
1210 PDFRadialGradient
1211};
1212
1213/*
1214PDF tiling pattern support. Uncolored only.
1215 */
1216
1217const underlyingColorSpaces = ['DeviceCMYK', 'DeviceRGB'];
1218class PDFTilingPattern {
1219 constructor(doc, bBox, xStep, yStep, stream) {
1220 this.doc = doc;
1221 this.bBox = bBox;
1222 this.xStep = xStep;
1223 this.yStep = yStep;
1224 this.stream = stream;
1225 }
1226 createPattern() {
1227 // no resources needed for our current usage
1228 // required entry
1229 const resources = this.doc.ref();
1230 resources.end();
1231 // apply default transform matrix (flipped in the default doc._ctm)
1232 // see document.js & gradient.js
1233 const [m0, m1, m2, m3, m4, m5] = this.doc._ctm;
1234 const [m11, m12, m21, m22, dx, dy] = [1, 0, 0, 1, 0, 0];
1235 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];
1236 const pattern = this.doc.ref({
1237 Type: 'Pattern',
1238 PatternType: 1,
1239 // tiling
1240 PaintType: 2,
1241 // 1-colored, 2-uncolored
1242 TilingType: 2,
1243 // 2-no distortion
1244 BBox: this.bBox,
1245 XStep: this.xStep,
1246 YStep: this.yStep,
1247 Matrix: m.map(v => +v.toFixed(5)),
1248 Resources: resources
1249 });
1250 pattern.end(this.stream);
1251 return pattern;
1252 }
1253 embedPatternColorSpaces() {
1254 // map each pattern to an underlying color space
1255 // and embed on each page
1256 underlyingColorSpaces.forEach(csName => {
1257 const csId = this.getPatternColorSpaceId(csName);
1258 if (this.doc.page.colorSpaces[csId]) return;
1259 const cs = this.doc.ref(['Pattern', csName]);
1260 cs.end();
1261 this.doc.page.colorSpaces[csId] = cs;
1262 });
1263 }
1264 getPatternColorSpaceId(underlyingColorspace) {
1265 return `CsP${underlyingColorspace}`;
1266 }
1267 embed() {
1268 if (!this.id) {
1269 this.doc._patternCount = this.doc._patternCount + 1;
1270 this.id = 'P' + this.doc._patternCount;
1271 this.pattern = this.createPattern();
1272 }
1273
1274 // patterns are embedded in each page
1275 if (!this.doc.page.patterns[this.id]) {
1276 this.doc.page.patterns[this.id] = this.pattern;
1277 }
1278 }
1279 apply(stroke, patternColor) {
1280 // do any embedding/creating that might be needed
1281 this.embedPatternColorSpaces();
1282 this.embed();
1283 const normalizedColor = this.doc._normalizeColor(patternColor);
1284 if (!normalizedColor) throw Error(`invalid pattern color. (value: ${patternColor})`);
1285
1286 // select one of the pattern color spaces
1287 const csId = this.getPatternColorSpaceId(this.doc._getColorSpace(normalizedColor));
1288 this.doc._setColorSpace(csId, stroke);
1289
1290 // stroke/fill using the pattern and color (in the above underlying color space)
1291 const op = stroke ? 'SCN' : 'scn';
1292 return this.doc.addContent(`${normalizedColor.join(' ')} /${this.id} ${op}`);
1293 }
1294}
1295var pattern = {
1296 PDFTilingPattern
1297};
1298
1299const {
1300 PDFGradient: PDFGradient$1,
1301 PDFLinearGradient: PDFLinearGradient$1,
1302 PDFRadialGradient: PDFRadialGradient$1
1303} = Gradient;
1304const {
1305 PDFTilingPattern: PDFTilingPattern$1
1306} = pattern;
1307var ColorMixin = {
1308 initColor() {
1309 // The opacity dictionaries
1310 this._opacityRegistry = {};
1311 this._opacityCount = 0;
1312 this._patternCount = 0;
1313 return this._gradCount = 0;
1314 },
1315 _normalizeColor(color) {
1316 if (typeof color === 'string') {
1317 if (color.charAt(0) === '#') {
1318 if (color.length === 4) {
1319 color = color.replace(/#([0-9A-F])([0-9A-F])([0-9A-F])/i, '#$1$1$2$2$3$3');
1320 }
1321 const hex = parseInt(color.slice(1), 16);
1322 color = [hex >> 16, hex >> 8 & 0xff, hex & 0xff];
1323 } else if (namedColors[color]) {
1324 color = namedColors[color];
1325 }
1326 }
1327 if (Array.isArray(color)) {
1328 // RGB
1329 if (color.length === 3) {
1330 color = color.map(part => part / 255);
1331 // CMYK
1332 } else if (color.length === 4) {
1333 color = color.map(part => part / 100);
1334 }
1335 return color;
1336 }
1337 return null;
1338 },
1339 _setColor(color, stroke) {
1340 if (color instanceof PDFGradient$1) {
1341 color.apply(stroke);
1342 return true;
1343 // see if tiling pattern, decode & apply it it
1344 } else if (Array.isArray(color) && color[0] instanceof PDFTilingPattern$1) {
1345 color[0].apply(stroke, color[1]);
1346 return true;
1347 }
1348 // any other case should be a normal color and not a pattern
1349 return this._setColorCore(color, stroke);
1350 },
1351 _setColorCore(color, stroke) {
1352 color = this._normalizeColor(color);
1353 if (!color) {
1354 return false;
1355 }
1356 const op = stroke ? 'SCN' : 'scn';
1357 const space = this._getColorSpace(color);
1358 this._setColorSpace(space, stroke);
1359 color = color.join(' ');
1360 this.addContent(`${color} ${op}`);
1361 return true;
1362 },
1363 _setColorSpace(space, stroke) {
1364 const op = stroke ? 'CS' : 'cs';
1365 return this.addContent(`/${space} ${op}`);
1366 },
1367 _getColorSpace(color) {
1368 return color.length === 4 ? 'DeviceCMYK' : 'DeviceRGB';
1369 },
1370 fillColor(color, opacity) {
1371 const set = this._setColor(color, false);
1372 if (set) {
1373 this.fillOpacity(opacity);
1374 }
1375
1376 // save this for text wrapper, which needs to reset
1377 // the fill color on new pages
1378 this._fillColor = [color, opacity];
1379 return this;
1380 },
1381 strokeColor(color, opacity) {
1382 const set = this._setColor(color, true);
1383 if (set) {
1384 this.strokeOpacity(opacity);
1385 }
1386 return this;
1387 },
1388 opacity(opacity) {
1389 this._doOpacity(opacity, opacity);
1390 return this;
1391 },
1392 fillOpacity(opacity) {
1393 this._doOpacity(opacity, null);
1394 return this;
1395 },
1396 strokeOpacity(opacity) {
1397 this._doOpacity(null, opacity);
1398 return this;
1399 },
1400 _doOpacity(fillOpacity, strokeOpacity) {
1401 let dictionary, name;
1402 if (fillOpacity == null && strokeOpacity == null) {
1403 return;
1404 }
1405 if (fillOpacity != null) {
1406 fillOpacity = Math.max(0, Math.min(1, fillOpacity));
1407 }
1408 if (strokeOpacity != null) {
1409 strokeOpacity = Math.max(0, Math.min(1, strokeOpacity));
1410 }
1411 const key = `${fillOpacity}_${strokeOpacity}`;
1412 if (this._opacityRegistry[key]) {
1413 [dictionary, name] = this._opacityRegistry[key];
1414 } else {
1415 dictionary = {
1416 Type: 'ExtGState'
1417 };
1418 if (fillOpacity != null) {
1419 dictionary.ca = fillOpacity;
1420 }
1421 if (strokeOpacity != null) {
1422 dictionary.CA = strokeOpacity;
1423 }
1424 dictionary = this.ref(dictionary);
1425 dictionary.end();
1426 const id = ++this._opacityCount;
1427 name = `Gs${id}`;
1428 this._opacityRegistry[key] = [dictionary, name];
1429 }
1430 this.page.ext_gstates[name] = dictionary;
1431 return this.addContent(`/${name} gs`);
1432 },
1433 linearGradient(x1, y1, x2, y2) {
1434 return new PDFLinearGradient$1(this, x1, y1, x2, y2);
1435 },
1436 radialGradient(x1, y1, r1, x2, y2, r2) {
1437 return new PDFRadialGradient$1(this, x1, y1, r1, x2, y2, r2);
1438 },
1439 pattern(bbox, xStep, yStep, stream) {
1440 return new PDFTilingPattern$1(this, bbox, xStep, yStep, stream);
1441 }
1442};
1443var namedColors = {
1444 aliceblue: [240, 248, 255],
1445 antiquewhite: [250, 235, 215],
1446 aqua: [0, 255, 255],
1447 aquamarine: [127, 255, 212],
1448 azure: [240, 255, 255],
1449 beige: [245, 245, 220],
1450 bisque: [255, 228, 196],
1451 black: [0, 0, 0],
1452 blanchedalmond: [255, 235, 205],
1453 blue: [0, 0, 255],
1454 blueviolet: [138, 43, 226],
1455 brown: [165, 42, 42],
1456 burlywood: [222, 184, 135],
1457 cadetblue: [95, 158, 160],
1458 chartreuse: [127, 255, 0],
1459 chocolate: [210, 105, 30],
1460 coral: [255, 127, 80],
1461 cornflowerblue: [100, 149, 237],
1462 cornsilk: [255, 248, 220],
1463 crimson: [220, 20, 60],
1464 cyan: [0, 255, 255],
1465 darkblue: [0, 0, 139],
1466 darkcyan: [0, 139, 139],
1467 darkgoldenrod: [184, 134, 11],
1468 darkgray: [169, 169, 169],
1469 darkgreen: [0, 100, 0],
1470 darkgrey: [169, 169, 169],
1471 darkkhaki: [189, 183, 107],
1472 darkmagenta: [139, 0, 139],
1473 darkolivegreen: [85, 107, 47],
1474 darkorange: [255, 140, 0],
1475 darkorchid: [153, 50, 204],
1476 darkred: [139, 0, 0],
1477 darksalmon: [233, 150, 122],
1478 darkseagreen: [143, 188, 143],
1479 darkslateblue: [72, 61, 139],
1480 darkslategray: [47, 79, 79],
1481 darkslategrey: [47, 79, 79],
1482 darkturquoise: [0, 206, 209],
1483 darkviolet: [148, 0, 211],
1484 deeppink: [255, 20, 147],
1485 deepskyblue: [0, 191, 255],
1486 dimgray: [105, 105, 105],
1487 dimgrey: [105, 105, 105],
1488 dodgerblue: [30, 144, 255],
1489 firebrick: [178, 34, 34],
1490 floralwhite: [255, 250, 240],
1491 forestgreen: [34, 139, 34],
1492 fuchsia: [255, 0, 255],
1493 gainsboro: [220, 220, 220],
1494 ghostwhite: [248, 248, 255],
1495 gold: [255, 215, 0],
1496 goldenrod: [218, 165, 32],
1497 gray: [128, 128, 128],
1498 grey: [128, 128, 128],
1499 green: [0, 128, 0],
1500 greenyellow: [173, 255, 47],
1501 honeydew: [240, 255, 240],
1502 hotpink: [255, 105, 180],
1503 indianred: [205, 92, 92],
1504 indigo: [75, 0, 130],
1505 ivory: [255, 255, 240],
1506 khaki: [240, 230, 140],
1507 lavender: [230, 230, 250],
1508 lavenderblush: [255, 240, 245],
1509 lawngreen: [124, 252, 0],
1510 lemonchiffon: [255, 250, 205],
1511 lightblue: [173, 216, 230],
1512 lightcoral: [240, 128, 128],
1513 lightcyan: [224, 255, 255],
1514 lightgoldenrodyellow: [250, 250, 210],
1515 lightgray: [211, 211, 211],
1516 lightgreen: [144, 238, 144],
1517 lightgrey: [211, 211, 211],
1518 lightpink: [255, 182, 193],
1519 lightsalmon: [255, 160, 122],
1520 lightseagreen: [32, 178, 170],
1521 lightskyblue: [135, 206, 250],
1522 lightslategray: [119, 136, 153],
1523 lightslategrey: [119, 136, 153],
1524 lightsteelblue: [176, 196, 222],
1525 lightyellow: [255, 255, 224],
1526 lime: [0, 255, 0],
1527 limegreen: [50, 205, 50],
1528 linen: [250, 240, 230],
1529 magenta: [255, 0, 255],
1530 maroon: [128, 0, 0],
1531 mediumaquamarine: [102, 205, 170],
1532 mediumblue: [0, 0, 205],
1533 mediumorchid: [186, 85, 211],
1534 mediumpurple: [147, 112, 219],
1535 mediumseagreen: [60, 179, 113],
1536 mediumslateblue: [123, 104, 238],
1537 mediumspringgreen: [0, 250, 154],
1538 mediumturquoise: [72, 209, 204],
1539 mediumvioletred: [199, 21, 133],
1540 midnightblue: [25, 25, 112],
1541 mintcream: [245, 255, 250],
1542 mistyrose: [255, 228, 225],
1543 moccasin: [255, 228, 181],
1544 navajowhite: [255, 222, 173],
1545 navy: [0, 0, 128],
1546 oldlace: [253, 245, 230],
1547 olive: [128, 128, 0],
1548 olivedrab: [107, 142, 35],
1549 orange: [255, 165, 0],
1550 orangered: [255, 69, 0],
1551 orchid: [218, 112, 214],
1552 palegoldenrod: [238, 232, 170],
1553 palegreen: [152, 251, 152],
1554 paleturquoise: [175, 238, 238],
1555 palevioletred: [219, 112, 147],
1556 papayawhip: [255, 239, 213],
1557 peachpuff: [255, 218, 185],
1558 peru: [205, 133, 63],
1559 pink: [255, 192, 203],
1560 plum: [221, 160, 221],
1561 powderblue: [176, 224, 230],
1562 purple: [128, 0, 128],
1563 red: [255, 0, 0],
1564 rosybrown: [188, 143, 143],
1565 royalblue: [65, 105, 225],
1566 saddlebrown: [139, 69, 19],
1567 salmon: [250, 128, 114],
1568 sandybrown: [244, 164, 96],
1569 seagreen: [46, 139, 87],
1570 seashell: [255, 245, 238],
1571 sienna: [160, 82, 45],
1572 silver: [192, 192, 192],
1573 skyblue: [135, 206, 235],
1574 slateblue: [106, 90, 205],
1575 slategray: [112, 128, 144],
1576 slategrey: [112, 128, 144],
1577 snow: [255, 250, 250],
1578 springgreen: [0, 255, 127],
1579 steelblue: [70, 130, 180],
1580 tan: [210, 180, 140],
1581 teal: [0, 128, 128],
1582 thistle: [216, 191, 216],
1583 tomato: [255, 99, 71],
1584 turquoise: [64, 224, 208],
1585 violet: [238, 130, 238],
1586 wheat: [245, 222, 179],
1587 white: [255, 255, 255],
1588 whitesmoke: [245, 245, 245],
1589 yellow: [255, 255, 0],
1590 yellowgreen: [154, 205, 50]
1591};
1592
1593let cx, cy, px, py, sx, sy;
1594cx = cy = px = py = sx = sy = 0;
1595const parameters = {
1596 A: 7,
1597 a: 7,
1598 C: 6,
1599 c: 6,
1600 H: 1,
1601 h: 1,
1602 L: 2,
1603 l: 2,
1604 M: 2,
1605 m: 2,
1606 Q: 4,
1607 q: 4,
1608 S: 4,
1609 s: 4,
1610 T: 2,
1611 t: 2,
1612 V: 1,
1613 v: 1,
1614 Z: 0,
1615 z: 0
1616};
1617const parse = function (path) {
1618 let cmd;
1619 const ret = [];
1620 let args = [];
1621 let curArg = '';
1622 let foundDecimal = false;
1623 let params = 0;
1624 for (let c of path) {
1625 if (parameters[c] != null) {
1626 params = parameters[c];
1627 if (cmd) {
1628 // save existing command
1629 if (curArg.length > 0) {
1630 args[args.length] = +curArg;
1631 }
1632 ret[ret.length] = {
1633 cmd,
1634 args
1635 };
1636 args = [];
1637 curArg = '';
1638 foundDecimal = false;
1639 }
1640 cmd = c;
1641 } else if ([' ', ','].includes(c) || c === '-' && curArg.length > 0 && curArg[curArg.length - 1] !== 'e' || c === '.' && foundDecimal) {
1642 if (curArg.length === 0) {
1643 continue;
1644 }
1645 if (args.length === params) {
1646 // handle reused commands
1647 ret[ret.length] = {
1648 cmd,
1649 args
1650 };
1651 args = [+curArg];
1652
1653 // handle assumed commands
1654 if (cmd === 'M') {
1655 cmd = 'L';
1656 }
1657 if (cmd === 'm') {
1658 cmd = 'l';
1659 }
1660 } else {
1661 args[args.length] = +curArg;
1662 }
1663 foundDecimal = c === '.';
1664
1665 // fix for negative numbers or repeated decimals with no delimeter between commands
1666 curArg = ['-', '.'].includes(c) ? c : '';
1667 } else {
1668 curArg += c;
1669 if (c === '.') {
1670 foundDecimal = true;
1671 }
1672 }
1673 }
1674
1675 // add the last command
1676 if (curArg.length > 0) {
1677 if (args.length === params) {
1678 // handle reused commands
1679 ret[ret.length] = {
1680 cmd,
1681 args
1682 };
1683 args = [+curArg];
1684
1685 // handle assumed commands
1686 if (cmd === 'M') {
1687 cmd = 'L';
1688 }
1689 if (cmd === 'm') {
1690 cmd = 'l';
1691 }
1692 } else {
1693 args[args.length] = +curArg;
1694 }
1695 }
1696 ret[ret.length] = {
1697 cmd,
1698 args
1699 };
1700 return ret;
1701};
1702const apply = function (commands, doc) {
1703 // current point, control point, and subpath starting point
1704 cx = cy = px = py = sx = sy = 0;
1705
1706 // run the commands
1707 for (let i = 0; i < commands.length; i++) {
1708 const c = commands[i];
1709 if (typeof runners[c.cmd] === 'function') {
1710 runners[c.cmd](doc, c.args);
1711 }
1712 }
1713};
1714const runners = {
1715 M(doc, a) {
1716 cx = a[0];
1717 cy = a[1];
1718 px = py = null;
1719 sx = cx;
1720 sy = cy;
1721 return doc.moveTo(cx, cy);
1722 },
1723 m(doc, a) {
1724 cx += a[0];
1725 cy += a[1];
1726 px = py = null;
1727 sx = cx;
1728 sy = cy;
1729 return doc.moveTo(cx, cy);
1730 },
1731 C(doc, a) {
1732 cx = a[4];
1733 cy = a[5];
1734 px = a[2];
1735 py = a[3];
1736 return doc.bezierCurveTo(...a);
1737 },
1738 c(doc, a) {
1739 doc.bezierCurveTo(a[0] + cx, a[1] + cy, a[2] + cx, a[3] + cy, a[4] + cx, a[5] + cy);
1740 px = cx + a[2];
1741 py = cy + a[3];
1742 cx += a[4];
1743 return cy += a[5];
1744 },
1745 S(doc, a) {
1746 if (px === null) {
1747 px = cx;
1748 py = cy;
1749 }
1750 doc.bezierCurveTo(cx - (px - cx), cy - (py - cy), a[0], a[1], a[2], a[3]);
1751 px = a[0];
1752 py = a[1];
1753 cx = a[2];
1754 return cy = a[3];
1755 },
1756 s(doc, a) {
1757 if (px === null) {
1758 px = cx;
1759 py = cy;
1760 }
1761 doc.bezierCurveTo(cx - (px - cx), cy - (py - cy), cx + a[0], cy + a[1], cx + a[2], cy + a[3]);
1762 px = cx + a[0];
1763 py = cy + a[1];
1764 cx += a[2];
1765 return cy += a[3];
1766 },
1767 Q(doc, a) {
1768 px = a[0];
1769 py = a[1];
1770 cx = a[2];
1771 cy = a[3];
1772 return doc.quadraticCurveTo(a[0], a[1], cx, cy);
1773 },
1774 q(doc, a) {
1775 doc.quadraticCurveTo(a[0] + cx, a[1] + cy, a[2] + cx, a[3] + cy);
1776 px = cx + a[0];
1777 py = cy + a[1];
1778 cx += a[2];
1779 return cy += a[3];
1780 },
1781 T(doc, a) {
1782 if (px === null) {
1783 px = cx;
1784 py = cy;
1785 } else {
1786 px = cx - (px - cx);
1787 py = cy - (py - cy);
1788 }
1789 doc.quadraticCurveTo(px, py, a[0], a[1]);
1790 px = cx - (px - cx);
1791 py = cy - (py - cy);
1792 cx = a[0];
1793 return cy = a[1];
1794 },
1795 t(doc, a) {
1796 if (px === null) {
1797 px = cx;
1798 py = cy;
1799 } else {
1800 px = cx - (px - cx);
1801 py = cy - (py - cy);
1802 }
1803 doc.quadraticCurveTo(px, py, cx + a[0], cy + a[1]);
1804 cx += a[0];
1805 return cy += a[1];
1806 },
1807 A(doc, a) {
1808 solveArc(doc, cx, cy, a);
1809 cx = a[5];
1810 return cy = a[6];
1811 },
1812 a(doc, a) {
1813 a[5] += cx;
1814 a[6] += cy;
1815 solveArc(doc, cx, cy, a);
1816 cx = a[5];
1817 return cy = a[6];
1818 },
1819 L(doc, a) {
1820 cx = a[0];
1821 cy = a[1];
1822 px = py = null;
1823 return doc.lineTo(cx, cy);
1824 },
1825 l(doc, a) {
1826 cx += a[0];
1827 cy += a[1];
1828 px = py = null;
1829 return doc.lineTo(cx, cy);
1830 },
1831 H(doc, a) {
1832 cx = a[0];
1833 px = py = null;
1834 return doc.lineTo(cx, cy);
1835 },
1836 h(doc, a) {
1837 cx += a[0];
1838 px = py = null;
1839 return doc.lineTo(cx, cy);
1840 },
1841 V(doc, a) {
1842 cy = a[0];
1843 px = py = null;
1844 return doc.lineTo(cx, cy);
1845 },
1846 v(doc, a) {
1847 cy += a[0];
1848 px = py = null;
1849 return doc.lineTo(cx, cy);
1850 },
1851 Z(doc) {
1852 doc.closePath();
1853 cx = sx;
1854 return cy = sy;
1855 },
1856 z(doc) {
1857 doc.closePath();
1858 cx = sx;
1859 return cy = sy;
1860 }
1861};
1862const solveArc = function (doc, x, y, coords) {
1863 const [rx, ry, rot, large, sweep, ex, ey] = coords;
1864 const segs = arcToSegments(ex, ey, rx, ry, large, sweep, rot, x, y);
1865 for (let seg of segs) {
1866 const bez = segmentToBezier(...seg);
1867 doc.bezierCurveTo(...bez);
1868 }
1869};
1870
1871// from Inkscape svgtopdf, thanks!
1872const arcToSegments = function (x, y, rx, ry, large, sweep, rotateX, ox, oy) {
1873 const th = rotateX * (Math.PI / 180);
1874 const sin_th = Math.sin(th);
1875 const cos_th = Math.cos(th);
1876 rx = Math.abs(rx);
1877 ry = Math.abs(ry);
1878 px = cos_th * (ox - x) * 0.5 + sin_th * (oy - y) * 0.5;
1879 py = cos_th * (oy - y) * 0.5 - sin_th * (ox - x) * 0.5;
1880 let pl = px * px / (rx * rx) + py * py / (ry * ry);
1881 if (pl > 1) {
1882 pl = Math.sqrt(pl);
1883 rx *= pl;
1884 ry *= pl;
1885 }
1886 const a00 = cos_th / rx;
1887 const a01 = sin_th / rx;
1888 const a10 = -sin_th / ry;
1889 const a11 = cos_th / ry;
1890 const x0 = a00 * ox + a01 * oy;
1891 const y0 = a10 * ox + a11 * oy;
1892 const x1 = a00 * x + a01 * y;
1893 const y1 = a10 * x + a11 * y;
1894 const d = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0);
1895 let sfactor_sq = 1 / d - 0.25;
1896 if (sfactor_sq < 0) {
1897 sfactor_sq = 0;
1898 }
1899 let sfactor = Math.sqrt(sfactor_sq);
1900 if (sweep === large) {
1901 sfactor = -sfactor;
1902 }
1903 const xc = 0.5 * (x0 + x1) - sfactor * (y1 - y0);
1904 const yc = 0.5 * (y0 + y1) + sfactor * (x1 - x0);
1905 const th0 = Math.atan2(y0 - yc, x0 - xc);
1906 const th1 = Math.atan2(y1 - yc, x1 - xc);
1907 let th_arc = th1 - th0;
1908 if (th_arc < 0 && sweep === 1) {
1909 th_arc += 2 * Math.PI;
1910 } else if (th_arc > 0 && sweep === 0) {
1911 th_arc -= 2 * Math.PI;
1912 }
1913 const segments = Math.ceil(Math.abs(th_arc / (Math.PI * 0.5 + 0.001)));
1914 const result = [];
1915 for (let i = 0; i < segments; i++) {
1916 const th2 = th0 + i * th_arc / segments;
1917 const th3 = th0 + (i + 1) * th_arc / segments;
1918 result[i] = [xc, yc, th2, th3, rx, ry, sin_th, cos_th];
1919 }
1920 return result;
1921};
1922const segmentToBezier = function (cx, cy, th0, th1, rx, ry, sin_th, cos_th) {
1923 const a00 = cos_th * rx;
1924 const a01 = -sin_th * ry;
1925 const a10 = sin_th * rx;
1926 const a11 = cos_th * ry;
1927 const th_half = 0.5 * (th1 - th0);
1928 const t = 8 / 3 * Math.sin(th_half * 0.5) * Math.sin(th_half * 0.5) / Math.sin(th_half);
1929 const x1 = cx + Math.cos(th0) - t * Math.sin(th0);
1930 const y1 = cy + Math.sin(th0) + t * Math.cos(th0);
1931 const x3 = cx + Math.cos(th1);
1932 const y3 = cy + Math.sin(th1);
1933 const x2 = x3 + t * Math.sin(th1);
1934 const y2 = y3 - t * Math.cos(th1);
1935 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];
1936};
1937class SVGPath {
1938 static apply(doc, path) {
1939 const commands = parse(path);
1940 apply(commands, doc);
1941 }
1942}
1943
1944const {
1945 number: number$1
1946} = PDFObject;
1947
1948// This constant is used to approximate a symmetrical arc using a cubic
1949// Bezier curve.
1950const KAPPA = 4.0 * ((Math.sqrt(2) - 1.0) / 3.0);
1951var VectorMixin = {
1952 initVector() {
1953 this._ctm = [1, 0, 0, 1, 0, 0]; // current transformation matrix
1954 return this._ctmStack = [];
1955 },
1956 save() {
1957 this._ctmStack.push(this._ctm.slice());
1958 // TODO: save/restore colorspace and styles so not setting it unnessesarily all the time?
1959 return this.addContent('q');
1960 },
1961 restore() {
1962 this._ctm = this._ctmStack.pop() || [1, 0, 0, 1, 0, 0];
1963 return this.addContent('Q');
1964 },
1965 closePath() {
1966 return this.addContent('h');
1967 },
1968 lineWidth(w) {
1969 return this.addContent(`${number$1(w)} w`);
1970 },
1971 _CAP_STYLES: {
1972 BUTT: 0,
1973 ROUND: 1,
1974 SQUARE: 2
1975 },
1976 lineCap(c) {
1977 if (typeof c === 'string') {
1978 c = this._CAP_STYLES[c.toUpperCase()];
1979 }
1980 return this.addContent(`${c} J`);
1981 },
1982 _JOIN_STYLES: {
1983 MITER: 0,
1984 ROUND: 1,
1985 BEVEL: 2
1986 },
1987 lineJoin(j) {
1988 if (typeof j === 'string') {
1989 j = this._JOIN_STYLES[j.toUpperCase()];
1990 }
1991 return this.addContent(`${j} j`);
1992 },
1993 miterLimit(m) {
1994 return this.addContent(`${number$1(m)} M`);
1995 },
1996 dash(length, options = {}) {
1997 const originalLength = length;
1998 if (!Array.isArray(length)) {
1999 length = [length, options.space || length];
2000 }
2001 const valid = length.every(x => Number.isFinite(x) && x > 0);
2002 if (!valid) {
2003 throw new Error(`dash(${JSON.stringify(originalLength)}, ${JSON.stringify(options)}) invalid, lengths must be numeric and greater than zero`);
2004 }
2005 length = length.map(number$1).join(' ');
2006 return this.addContent(`[${length}] ${number$1(options.phase || 0)} d`);
2007 },
2008 undash() {
2009 return this.addContent('[] 0 d');
2010 },
2011 moveTo(x, y) {
2012 return this.addContent(`${number$1(x)} ${number$1(y)} m`);
2013 },
2014 lineTo(x, y) {
2015 return this.addContent(`${number$1(x)} ${number$1(y)} l`);
2016 },
2017 bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) {
2018 return this.addContent(`${number$1(cp1x)} ${number$1(cp1y)} ${number$1(cp2x)} ${number$1(cp2y)} ${number$1(x)} ${number$1(y)} c`);
2019 },
2020 quadraticCurveTo(cpx, cpy, x, y) {
2021 return this.addContent(`${number$1(cpx)} ${number$1(cpy)} ${number$1(x)} ${number$1(y)} v`);
2022 },
2023 rect(x, y, w, h) {
2024 return this.addContent(`${number$1(x)} ${number$1(y)} ${number$1(w)} ${number$1(h)} re`);
2025 },
2026 roundedRect(x, y, w, h, r) {
2027 if (r == null) {
2028 r = 0;
2029 }
2030 r = Math.min(r, 0.5 * w, 0.5 * h);
2031
2032 // amount to inset control points from corners (see `ellipse`)
2033 const c = r * (1.0 - KAPPA);
2034 this.moveTo(x + r, y);
2035 this.lineTo(x + w - r, y);
2036 this.bezierCurveTo(x + w - c, y, x + w, y + c, x + w, y + r);
2037 this.lineTo(x + w, y + h - r);
2038 this.bezierCurveTo(x + w, y + h - c, x + w - c, y + h, x + w - r, y + h);
2039 this.lineTo(x + r, y + h);
2040 this.bezierCurveTo(x + c, y + h, x, y + h - c, x, y + h - r);
2041 this.lineTo(x, y + r);
2042 this.bezierCurveTo(x, y + c, x + c, y, x + r, y);
2043 return this.closePath();
2044 },
2045 ellipse(x, y, r1, r2) {
2046 // based on http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas/2173084#2173084
2047 if (r2 == null) {
2048 r2 = r1;
2049 }
2050 x -= r1;
2051 y -= r2;
2052 const ox = r1 * KAPPA;
2053 const oy = r2 * KAPPA;
2054 const xe = x + r1 * 2;
2055 const ye = y + r2 * 2;
2056 const xm = x + r1;
2057 const ym = y + r2;
2058 this.moveTo(x, ym);
2059 this.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
2060 this.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
2061 this.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
2062 this.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
2063 return this.closePath();
2064 },
2065 circle(x, y, radius) {
2066 return this.ellipse(x, y, radius);
2067 },
2068 arc(x, y, radius, startAngle, endAngle, anticlockwise) {
2069 if (anticlockwise == null) {
2070 anticlockwise = false;
2071 }
2072 const TWO_PI = 2.0 * Math.PI;
2073 const HALF_PI = 0.5 * Math.PI;
2074 let deltaAng = endAngle - startAngle;
2075 if (Math.abs(deltaAng) > TWO_PI) {
2076 // draw only full circle if more than that is specified
2077 deltaAng = TWO_PI;
2078 } else if (deltaAng !== 0 && anticlockwise !== deltaAng < 0) {
2079 // necessary to flip direction of rendering
2080 const dir = anticlockwise ? -1 : 1;
2081 deltaAng = dir * TWO_PI + deltaAng;
2082 }
2083 const numSegs = Math.ceil(Math.abs(deltaAng) / HALF_PI);
2084 const segAng = deltaAng / numSegs;
2085 const handleLen = segAng / HALF_PI * KAPPA * radius;
2086 let curAng = startAngle;
2087
2088 // component distances between anchor point and control point
2089 let deltaCx = -Math.sin(curAng) * handleLen;
2090 let deltaCy = Math.cos(curAng) * handleLen;
2091
2092 // anchor point
2093 let ax = x + Math.cos(curAng) * radius;
2094 let ay = y + Math.sin(curAng) * radius;
2095
2096 // calculate and render segments
2097 this.moveTo(ax, ay);
2098 for (let segIdx = 0; segIdx < numSegs; segIdx++) {
2099 // starting control point
2100 const cp1x = ax + deltaCx;
2101 const cp1y = ay + deltaCy;
2102
2103 // step angle
2104 curAng += segAng;
2105
2106 // next anchor point
2107 ax = x + Math.cos(curAng) * radius;
2108 ay = y + Math.sin(curAng) * radius;
2109
2110 // next control point delta
2111 deltaCx = -Math.sin(curAng) * handleLen;
2112 deltaCy = Math.cos(curAng) * handleLen;
2113
2114 // ending control point
2115 const cp2x = ax - deltaCx;
2116 const cp2y = ay - deltaCy;
2117
2118 // render segment
2119 this.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, ax, ay);
2120 }
2121 return this;
2122 },
2123 polygon(...points) {
2124 this.moveTo(...(points.shift() || []));
2125 for (let point of points) {
2126 this.lineTo(...(point || []));
2127 }
2128 return this.closePath();
2129 },
2130 path(path) {
2131 SVGPath.apply(this, path);
2132 return this;
2133 },
2134 _windingRule(rule) {
2135 if (/even-?odd/.test(rule)) {
2136 return '*';
2137 }
2138 return '';
2139 },
2140 fill(color, rule) {
2141 if (/(even-?odd)|(non-?zero)/.test(color)) {
2142 rule = color;
2143 color = null;
2144 }
2145 if (color) {
2146 this.fillColor(color);
2147 }
2148 return this.addContent(`f${this._windingRule(rule)}`);
2149 },
2150 stroke(color) {
2151 if (color) {
2152 this.strokeColor(color);
2153 }
2154 return this.addContent('S');
2155 },
2156 fillAndStroke(fillColor, strokeColor, rule) {
2157 if (strokeColor == null) {
2158 strokeColor = fillColor;
2159 }
2160 const isFillRule = /(even-?odd)|(non-?zero)/;
2161 if (isFillRule.test(fillColor)) {
2162 rule = fillColor;
2163 fillColor = null;
2164 }
2165 if (isFillRule.test(strokeColor)) {
2166 rule = strokeColor;
2167 strokeColor = fillColor;
2168 }
2169 if (fillColor) {
2170 this.fillColor(fillColor);
2171 this.strokeColor(strokeColor);
2172 }
2173 return this.addContent(`B${this._windingRule(rule)}`);
2174 },
2175 clip(rule) {
2176 return this.addContent(`W${this._windingRule(rule)} n`);
2177 },
2178 transform(m11, m12, m21, m22, dx, dy) {
2179 // keep track of the current transformation matrix
2180 if (m11 === 1 && m12 === 0 && m21 === 0 && m22 === 1 && dx === 0 && dy === 0) {
2181 // Ignore identity transforms
2182 return this;
2183 }
2184 const m = this._ctm;
2185 const [m0, m1, m2, m3, m4, m5] = m;
2186 m[0] = m0 * m11 + m2 * m12;
2187 m[1] = m1 * m11 + m3 * m12;
2188 m[2] = m0 * m21 + m2 * m22;
2189 m[3] = m1 * m21 + m3 * m22;
2190 m[4] = m0 * dx + m2 * dy + m4;
2191 m[5] = m1 * dx + m3 * dy + m5;
2192 const values = [m11, m12, m21, m22, dx, dy].map(v => number$1(v)).join(' ');
2193 return this.addContent(`${values} cm`);
2194 },
2195 translate(x, y) {
2196 return this.transform(1, 0, 0, 1, x, y);
2197 },
2198 rotate(angle, options = {}) {
2199 let y;
2200 const rad = angle * Math.PI / 180;
2201 const cos = Math.cos(rad);
2202 const sin = Math.sin(rad);
2203 let x = y = 0;
2204 if (options.origin != null) {
2205 [x, y] = options.origin;
2206 const x1 = x * cos - y * sin;
2207 const y1 = x * sin + y * cos;
2208 x -= x1;
2209 y -= y1;
2210 }
2211 return this.transform(cos, sin, -sin, cos, x, y);
2212 },
2213 scale(xFactor, yFactor, options = {}) {
2214 let y;
2215 if (yFactor == null) {
2216 yFactor = xFactor;
2217 }
2218 if (typeof yFactor === 'object') {
2219 options = yFactor;
2220 yFactor = xFactor;
2221 }
2222 let x = y = 0;
2223 if (options.origin != null) {
2224 [x, y] = options.origin;
2225 x -= xFactor * x;
2226 y -= yFactor * y;
2227 }
2228 return this.transform(xFactor, 0, 0, yFactor, x, y);
2229 }
2230};
2231
2232const WIN_ANSI_MAP = {
2233 402: 131,
2234 8211: 150,
2235 8212: 151,
2236 8216: 145,
2237 8217: 146,
2238 8218: 130,
2239 8220: 147,
2240 8221: 148,
2241 8222: 132,
2242 8224: 134,
2243 8225: 135,
2244 8226: 149,
2245 8230: 133,
2246 8364: 128,
2247 8240: 137,
2248 8249: 139,
2249 8250: 155,
2250 710: 136,
2251 8482: 153,
2252 338: 140,
2253 339: 156,
2254 732: 152,
2255 352: 138,
2256 353: 154,
2257 376: 159,
2258 381: 142,
2259 382: 158
2260};
2261const characters = `\
2262.notdef .notdef .notdef .notdef
2263.notdef .notdef .notdef .notdef
2264.notdef .notdef .notdef .notdef
2265.notdef .notdef .notdef .notdef
2266.notdef .notdef .notdef .notdef
2267.notdef .notdef .notdef .notdef
2268.notdef .notdef .notdef .notdef
2269.notdef .notdef .notdef .notdef
2270
2271space exclam quotedbl numbersign
2272dollar percent ampersand quotesingle
2273parenleft parenright asterisk plus
2274comma hyphen period slash
2275zero one two three
2276four five six seven
2277eight nine colon semicolon
2278less equal greater question
2279
2280at A B C
2281D E F G
2282H I J K
2283L M N O
2284P Q R S
2285T U V W
2286X Y Z bracketleft
2287backslash bracketright asciicircum underscore
2288
2289grave a b c
2290d e f g
2291h i j k
2292l m n o
2293p q r s
2294t u v w
2295x y z braceleft
2296bar braceright asciitilde .notdef
2297
2298Euro .notdef quotesinglbase florin
2299quotedblbase ellipsis dagger daggerdbl
2300circumflex perthousand Scaron guilsinglleft
2301OE .notdef Zcaron .notdef
2302.notdef quoteleft quoteright quotedblleft
2303quotedblright bullet endash emdash
2304tilde trademark scaron guilsinglright
2305oe .notdef zcaron ydieresis
2306
2307space exclamdown cent sterling
2308currency yen brokenbar section
2309dieresis copyright ordfeminine guillemotleft
2310logicalnot hyphen registered macron
2311degree plusminus twosuperior threesuperior
2312acute mu paragraph periodcentered
2313cedilla onesuperior ordmasculine guillemotright
2314onequarter onehalf threequarters questiondown
2315
2316Agrave Aacute Acircumflex Atilde
2317Adieresis Aring AE Ccedilla
2318Egrave Eacute Ecircumflex Edieresis
2319Igrave Iacute Icircumflex Idieresis
2320Eth Ntilde Ograve Oacute
2321Ocircumflex Otilde Odieresis multiply
2322Oslash Ugrave Uacute Ucircumflex
2323Udieresis Yacute Thorn germandbls
2324
2325agrave aacute acircumflex atilde
2326adieresis aring ae ccedilla
2327egrave eacute ecircumflex edieresis
2328igrave iacute icircumflex idieresis
2329eth ntilde ograve oacute
2330ocircumflex otilde odieresis divide
2331oslash ugrave uacute ucircumflex
2332udieresis yacute thorn ydieresis\
2333`.split(/\s+/);
2334class AFMFont {
2335 static open(filename) {
2336 return new AFMFont(fs.readFileSync(filename, 'utf8'));
2337 }
2338 constructor(contents) {
2339 this.contents = contents;
2340 this.attributes = {};
2341 this.glyphWidths = {};
2342 this.boundingBoxes = {};
2343 this.kernPairs = {};
2344 this.parse();
2345 // todo: remove charWidths since appears to not be used
2346 this.charWidths = new Array(256);
2347 for (let char = 0; char <= 255; char++) {
2348 this.charWidths[char] = this.glyphWidths[characters[char]];
2349 }
2350 this.bbox = this.attributes['FontBBox'].split(/\s+/).map(e => +e);
2351 this.ascender = +(this.attributes['Ascender'] || 0);
2352 this.descender = +(this.attributes['Descender'] || 0);
2353 this.xHeight = +(this.attributes['XHeight'] || 0);
2354 this.capHeight = +(this.attributes['CapHeight'] || 0);
2355 this.lineGap = this.bbox[3] - this.bbox[1] - (this.ascender - this.descender);
2356 }
2357 parse() {
2358 let section = '';
2359 for (let line of this.contents.split('\n')) {
2360 var match;
2361 var a;
2362 if (match = line.match(/^Start(\w+)/)) {
2363 section = match[1];
2364 continue;
2365 } else if (match = line.match(/^End(\w+)/)) {
2366 section = '';
2367 continue;
2368 }
2369 switch (section) {
2370 case 'FontMetrics':
2371 match = line.match(/(^\w+)\s+(.*)/);
2372 var key = match[1];
2373 var value = match[2];
2374 if (a = this.attributes[key]) {
2375 if (!Array.isArray(a)) {
2376 a = this.attributes[key] = [a];
2377 }
2378 a.push(value);
2379 } else {
2380 this.attributes[key] = value;
2381 }
2382 break;
2383 case 'CharMetrics':
2384 if (!/^CH?\s/.test(line)) {
2385 continue;
2386 }
2387 var name = line.match(/\bN\s+(\.?\w+)\s*;/)[1];
2388 this.glyphWidths[name] = +line.match(/\bWX\s+(\d+)\s*;/)[1];
2389 break;
2390 case 'KernPairs':
2391 match = line.match(/^KPX\s+(\.?\w+)\s+(\.?\w+)\s+(-?\d+)/);
2392 if (match) {
2393 this.kernPairs[match[1] + '\0' + match[2]] = parseInt(match[3]);
2394 }
2395 break;
2396 }
2397 }
2398 }
2399 encodeText(text) {
2400 const res = [];
2401 for (let i = 0, len = text.length; i < len; i++) {
2402 let char = text.charCodeAt(i);
2403 char = WIN_ANSI_MAP[char] || char;
2404 res.push(char.toString(16));
2405 }
2406 return res;
2407 }
2408 glyphsForString(string) {
2409 const glyphs = [];
2410 for (let i = 0, len = string.length; i < len; i++) {
2411 const charCode = string.charCodeAt(i);
2412 glyphs.push(this.characterToGlyph(charCode));
2413 }
2414 return glyphs;
2415 }
2416 characterToGlyph(character) {
2417 return characters[WIN_ANSI_MAP[character] || character] || '.notdef';
2418 }
2419 widthOfGlyph(glyph) {
2420 return this.glyphWidths[glyph] || 0;
2421 }
2422 getKernPair(left, right) {
2423 return this.kernPairs[left + '\0' + right] || 0;
2424 }
2425 advancesForGlyphs(glyphs) {
2426 const advances = [];
2427 for (let index = 0; index < glyphs.length; index++) {
2428 const left = glyphs[index];
2429 const right = glyphs[index + 1];
2430 advances.push(this.widthOfGlyph(left) + this.getKernPair(left, right));
2431 }
2432 return advances;
2433 }
2434}
2435
2436class PDFFont {
2437 constructor() {}
2438 encode() {
2439 throw new Error('Must be implemented by subclasses');
2440 }
2441 widthOfString() {
2442 throw new Error('Must be implemented by subclasses');
2443 }
2444 ref() {
2445 return this.dictionary != null ? this.dictionary : this.dictionary = this.document.ref();
2446 }
2447 finalize() {
2448 if (this.embedded || this.dictionary == null) {
2449 return;
2450 }
2451 this.embed();
2452 return this.embedded = true;
2453 }
2454 embed() {
2455 throw new Error('Must be implemented by subclasses');
2456 }
2457 lineHeight(size, includeGap) {
2458 if (includeGap == null) {
2459 includeGap = false;
2460 }
2461 const gap = includeGap ? this.lineGap : 0;
2462 return (this.ascender + gap - this.descender) / 1000 * size;
2463 }
2464}
2465
2466// This insanity is so bundlers can inline the font files
2467const STANDARD_FONTS = {
2468 Courier() {
2469 return fs.readFileSync(__dirname + '/data/Courier.afm', 'utf8');
2470 },
2471 'Courier-Bold'() {
2472 return fs.readFileSync(__dirname + '/data/Courier-Bold.afm', 'utf8');
2473 },
2474 'Courier-Oblique'() {
2475 return fs.readFileSync(__dirname + '/data/Courier-Oblique.afm', 'utf8');
2476 },
2477 'Courier-BoldOblique'() {
2478 return fs.readFileSync(__dirname + '/data/Courier-BoldOblique.afm', 'utf8');
2479 },
2480 Helvetica() {
2481 return fs.readFileSync(__dirname + '/data/Helvetica.afm', 'utf8');
2482 },
2483 'Helvetica-Bold'() {
2484 return fs.readFileSync(__dirname + '/data/Helvetica-Bold.afm', 'utf8');
2485 },
2486 'Helvetica-Oblique'() {
2487 return fs.readFileSync(__dirname + '/data/Helvetica-Oblique.afm', 'utf8');
2488 },
2489 'Helvetica-BoldOblique'() {
2490 return fs.readFileSync(__dirname + '/data/Helvetica-BoldOblique.afm', 'utf8');
2491 },
2492 'Times-Roman'() {
2493 return fs.readFileSync(__dirname + '/data/Times-Roman.afm', 'utf8');
2494 },
2495 'Times-Bold'() {
2496 return fs.readFileSync(__dirname + '/data/Times-Bold.afm', 'utf8');
2497 },
2498 'Times-Italic'() {
2499 return fs.readFileSync(__dirname + '/data/Times-Italic.afm', 'utf8');
2500 },
2501 'Times-BoldItalic'() {
2502 return fs.readFileSync(__dirname + '/data/Times-BoldItalic.afm', 'utf8');
2503 },
2504 Symbol() {
2505 return fs.readFileSync(__dirname + '/data/Symbol.afm', 'utf8');
2506 },
2507 ZapfDingbats() {
2508 return fs.readFileSync(__dirname + '/data/ZapfDingbats.afm', 'utf8');
2509 }
2510};
2511class StandardFont extends PDFFont {
2512 constructor(document, name, id) {
2513 super();
2514 this.document = document;
2515 this.name = name;
2516 this.id = id;
2517 this.font = new AFMFont(STANDARD_FONTS[this.name]());
2518 ({
2519 ascender: this.ascender,
2520 descender: this.descender,
2521 bbox: this.bbox,
2522 lineGap: this.lineGap,
2523 xHeight: this.xHeight,
2524 capHeight: this.capHeight
2525 } = this.font);
2526 }
2527 embed() {
2528 this.dictionary.data = {
2529 Type: 'Font',
2530 BaseFont: this.name,
2531 Subtype: 'Type1',
2532 Encoding: 'WinAnsiEncoding'
2533 };
2534 return this.dictionary.end();
2535 }
2536 encode(text) {
2537 const encoded = this.font.encodeText(text);
2538 const glyphs = this.font.glyphsForString(`${text}`);
2539 const advances = this.font.advancesForGlyphs(glyphs);
2540 const positions = [];
2541 for (let i = 0; i < glyphs.length; i++) {
2542 const glyph = glyphs[i];
2543 positions.push({
2544 xAdvance: advances[i],
2545 yAdvance: 0,
2546 xOffset: 0,
2547 yOffset: 0,
2548 advanceWidth: this.font.widthOfGlyph(glyph)
2549 });
2550 }
2551 return [encoded, positions];
2552 }
2553 widthOfString(string, size) {
2554 const glyphs = this.font.glyphsForString(`${string}`);
2555 const advances = this.font.advancesForGlyphs(glyphs);
2556 let width = 0;
2557 for (let advance of advances) {
2558 width += advance;
2559 }
2560 const scale = size / 1000;
2561 return width * scale;
2562 }
2563 static isStandardFont(name) {
2564 return name in STANDARD_FONTS;
2565 }
2566}
2567
2568const toHex = function (num) {
2569 return `0000${num.toString(16)}`.slice(-4);
2570};
2571class EmbeddedFont extends PDFFont {
2572 constructor(document, font, id) {
2573 super();
2574 this.document = document;
2575 this.font = font;
2576 this.id = id;
2577 this.subset = this.font.createSubset();
2578 this.unicode = [[0]];
2579 this.widths = [this.font.getGlyph(0).advanceWidth];
2580 this.name = this.font.postscriptName;
2581 this.scale = 1000 / this.font.unitsPerEm;
2582 this.ascender = this.font.ascent * this.scale;
2583 this.descender = this.font.descent * this.scale;
2584 this.xHeight = this.font.xHeight * this.scale;
2585 this.capHeight = this.font.capHeight * this.scale;
2586 this.lineGap = this.font.lineGap * this.scale;
2587 this.bbox = this.font.bbox;
2588 if (document.options.fontLayoutCache !== false) {
2589 this.layoutCache = Object.create(null);
2590 }
2591 }
2592 layoutRun(text, features) {
2593 const run = this.font.layout(text, features);
2594
2595 // Normalize position values
2596 for (let i = 0; i < run.positions.length; i++) {
2597 const position = run.positions[i];
2598 for (let key in position) {
2599 position[key] *= this.scale;
2600 }
2601 position.advanceWidth = run.glyphs[i].advanceWidth * this.scale;
2602 }
2603 return run;
2604 }
2605 layoutCached(text) {
2606 if (!this.layoutCache) {
2607 return this.layoutRun(text);
2608 }
2609 let cached;
2610 if (cached = this.layoutCache[text]) {
2611 return cached;
2612 }
2613 const run = this.layoutRun(text);
2614 this.layoutCache[text] = run;
2615 return run;
2616 }
2617 layout(text, features, onlyWidth) {
2618 // Skip the cache if any user defined features are applied
2619 if (features) {
2620 return this.layoutRun(text, features);
2621 }
2622 let glyphs = onlyWidth ? null : [];
2623 let positions = onlyWidth ? null : [];
2624 let advanceWidth = 0;
2625
2626 // Split the string by words to increase cache efficiency.
2627 // For this purpose, spaces and tabs are a good enough delimeter.
2628 let last = 0;
2629 let index = 0;
2630 while (index <= text.length) {
2631 var needle;
2632 if (index === text.length && last < index || (needle = text.charAt(index), [' ', '\t'].includes(needle))) {
2633 const run = this.layoutCached(text.slice(last, ++index));
2634 if (!onlyWidth) {
2635 glyphs = glyphs.concat(run.glyphs);
2636 positions = positions.concat(run.positions);
2637 }
2638 advanceWidth += run.advanceWidth;
2639 last = index;
2640 } else {
2641 index++;
2642 }
2643 }
2644 return {
2645 glyphs,
2646 positions,
2647 advanceWidth
2648 };
2649 }
2650 encode(text, features) {
2651 const {
2652 glyphs,
2653 positions
2654 } = this.layout(text, features);
2655 const res = [];
2656 for (let i = 0; i < glyphs.length; i++) {
2657 const glyph = glyphs[i];
2658 const gid = this.subset.includeGlyph(glyph.id);
2659 res.push(`0000${gid.toString(16)}`.slice(-4));
2660 if (this.widths[gid] == null) {
2661 this.widths[gid] = glyph.advanceWidth * this.scale;
2662 }
2663 if (this.unicode[gid] == null) {
2664 this.unicode[gid] = glyph.codePoints;
2665 }
2666 }
2667 return [res, positions];
2668 }
2669 widthOfString(string, size, features) {
2670 const width = this.layout(string, features, true).advanceWidth;
2671 const scale = size / 1000;
2672 return width * scale;
2673 }
2674 embed() {
2675 const isCFF = this.subset.cff != null;
2676 const fontFile = this.document.ref();
2677 if (isCFF) {
2678 fontFile.data.Subtype = 'CIDFontType0C';
2679 }
2680 this.subset.encodeStream().on('data', data => fontFile.write(data)).on('end', () => fontFile.end());
2681 const familyClass = ((this.font['OS/2'] != null ? this.font['OS/2'].sFamilyClass : undefined) || 0) >> 8;
2682 let flags = 0;
2683 if (this.font.post.isFixedPitch) {
2684 flags |= 1 << 0;
2685 }
2686 if (1 <= familyClass && familyClass <= 7) {
2687 flags |= 1 << 1;
2688 }
2689 flags |= 1 << 2; // assume the font uses non-latin characters
2690 if (familyClass === 10) {
2691 flags |= 1 << 3;
2692 }
2693 if (this.font.head.macStyle.italic) {
2694 flags |= 1 << 6;
2695 }
2696
2697 // generate a tag (6 uppercase letters. 17 is the char code offset from '0' to 'A'. 73 will map to 'Z')
2698 const tag = [1, 2, 3, 4, 5, 6].map(i => String.fromCharCode((this.id.charCodeAt(i) || 73) + 17)).join('');
2699 const name = tag + '+' + this.font.postscriptName;
2700 const {
2701 bbox
2702 } = this.font;
2703 const descriptor = this.document.ref({
2704 Type: 'FontDescriptor',
2705 FontName: name,
2706 Flags: flags,
2707 FontBBox: [bbox.minX * this.scale, bbox.minY * this.scale, bbox.maxX * this.scale, bbox.maxY * this.scale],
2708 ItalicAngle: this.font.italicAngle,
2709 Ascent: this.ascender,
2710 Descent: this.descender,
2711 CapHeight: (this.font.capHeight || this.font.ascent) * this.scale,
2712 XHeight: (this.font.xHeight || 0) * this.scale,
2713 StemV: 0
2714 }); // not sure how to calculate this
2715
2716 if (isCFF) {
2717 descriptor.data.FontFile3 = fontFile;
2718 } else {
2719 descriptor.data.FontFile2 = fontFile;
2720 }
2721 if (this.document.subset) {
2722 const CIDSet = Buffer.from('FFFFFFFFC0', 'hex');
2723 const CIDSetRef = this.document.ref();
2724 CIDSetRef.write(CIDSet);
2725 CIDSetRef.end();
2726 descriptor.data.CIDSet = CIDSetRef;
2727 }
2728 descriptor.end();
2729 const descendantFontData = {
2730 Type: 'Font',
2731 Subtype: 'CIDFontType0',
2732 BaseFont: name,
2733 CIDSystemInfo: {
2734 Registry: new String('Adobe'),
2735 Ordering: new String('Identity'),
2736 Supplement: 0
2737 },
2738 FontDescriptor: descriptor,
2739 W: [0, this.widths]
2740 };
2741 if (!isCFF) {
2742 descendantFontData.Subtype = 'CIDFontType2';
2743 descendantFontData.CIDToGIDMap = 'Identity';
2744 }
2745 const descendantFont = this.document.ref(descendantFontData);
2746 descendantFont.end();
2747 this.dictionary.data = {
2748 Type: 'Font',
2749 Subtype: 'Type0',
2750 BaseFont: name,
2751 Encoding: 'Identity-H',
2752 DescendantFonts: [descendantFont],
2753 ToUnicode: this.toUnicodeCmap()
2754 };
2755 return this.dictionary.end();
2756 }
2757
2758 // Maps the glyph ids encoded in the PDF back to unicode strings
2759 // Because of ligature substitutions and the like, there may be one or more
2760 // unicode characters represented by each glyph.
2761 toUnicodeCmap() {
2762 const cmap = this.document.ref();
2763 const entries = [];
2764 for (let codePoints of this.unicode) {
2765 const encoded = [];
2766
2767 // encode codePoints to utf16
2768 for (let value of codePoints) {
2769 if (value > 0xffff) {
2770 value -= 0x10000;
2771 encoded.push(toHex(value >>> 10 & 0x3ff | 0xd800));
2772 value = 0xdc00 | value & 0x3ff;
2773 }
2774 encoded.push(toHex(value));
2775 }
2776 entries.push(`<${encoded.join(' ')}>`);
2777 }
2778 const chunkSize = 256;
2779 const chunks = Math.ceil(entries.length / chunkSize);
2780 const ranges = [];
2781 for (let i = 0; i < chunks; i++) {
2782 const start = i * chunkSize;
2783 const end = Math.min((i + 1) * chunkSize, entries.length);
2784 ranges.push(`<${toHex(start)}> <${toHex(end - 1)}> [${entries.slice(start, end).join(' ')}]`);
2785 }
2786 cmap.end(`\
2787/CIDInit /ProcSet findresource begin
278812 dict begin
2789begincmap
2790/CIDSystemInfo <<
2791 /Registry (Adobe)
2792 /Ordering (UCS)
2793 /Supplement 0
2794>> def
2795/CMapName /Adobe-Identity-UCS def
2796/CMapType 2 def
27971 begincodespacerange
2798<0000><ffff>
2799endcodespacerange
28001 beginbfrange
2801${ranges.join('\n')}
2802endbfrange
2803endcmap
2804CMapName currentdict /CMap defineresource pop
2805end
2806end\
2807`);
2808 return cmap;
2809 }
2810}
2811
2812class PDFFontFactory {
2813 static open(document, src, family, id) {
2814 let font;
2815 if (typeof src === 'string') {
2816 if (StandardFont.isStandardFont(src)) {
2817 return new StandardFont(document, src, id);
2818 }
2819 src = fs.readFileSync(src);
2820 }
2821 if (Buffer.isBuffer(src)) {
2822 font = fontkit.create(src, family);
2823 } else if (src instanceof Uint8Array) {
2824 font = fontkit.create(Buffer.from(src), family);
2825 } else if (src instanceof ArrayBuffer) {
2826 font = fontkit.create(Buffer.from(new Uint8Array(src)), family);
2827 }
2828 if (font == null) {
2829 throw new Error('Not a supported font format or standard PDF font.');
2830 }
2831 return new EmbeddedFont(document, font, id);
2832 }
2833}
2834
2835var FontsMixin = {
2836 initFonts(defaultFont = 'Helvetica') {
2837 // Lookup table for embedded fonts
2838 this._fontFamilies = {};
2839 this._fontCount = 0;
2840
2841 // Font state
2842 this._fontSize = 12;
2843 this._font = null;
2844 this._registeredFonts = {};
2845
2846 // Set the default font
2847 if (defaultFont) {
2848 this.font(defaultFont);
2849 }
2850 },
2851 font(src, family, size) {
2852 let cacheKey, font;
2853 if (typeof family === 'number') {
2854 size = family;
2855 family = null;
2856 }
2857
2858 // check registered fonts if src is a string
2859 if (typeof src === 'string' && this._registeredFonts[src]) {
2860 cacheKey = src;
2861 ({
2862 src,
2863 family
2864 } = this._registeredFonts[src]);
2865 } else {
2866 cacheKey = family || src;
2867 if (typeof cacheKey !== 'string') {
2868 cacheKey = null;
2869 }
2870 }
2871 if (size != null) {
2872 this.fontSize(size);
2873 }
2874
2875 // fast path: check if the font is already in the PDF
2876 if (font = this._fontFamilies[cacheKey]) {
2877 this._font = font;
2878 return this;
2879 }
2880
2881 // load the font
2882 const id = `F${++this._fontCount}`;
2883 this._font = PDFFontFactory.open(this, src, family, id);
2884
2885 // check for existing font familes with the same name already in the PDF
2886 // useful if the font was passed as a buffer
2887 if (font = this._fontFamilies[this._font.name]) {
2888 this._font = font;
2889 return this;
2890 }
2891
2892 // save the font for reuse later
2893 if (cacheKey) {
2894 this._fontFamilies[cacheKey] = this._font;
2895 }
2896 if (this._font.name) {
2897 this._fontFamilies[this._font.name] = this._font;
2898 }
2899 return this;
2900 },
2901 fontSize(_fontSize) {
2902 this._fontSize = _fontSize;
2903 return this;
2904 },
2905 currentLineHeight(includeGap) {
2906 if (includeGap == null) {
2907 includeGap = false;
2908 }
2909 return this._font.lineHeight(this._fontSize, includeGap);
2910 },
2911 registerFont(name, src, family) {
2912 this._registeredFonts[name] = {
2913 src,
2914 family
2915 };
2916 return this;
2917 }
2918};
2919
2920const SOFT_HYPHEN = '\u00AD';
2921const HYPHEN = '-';
2922class LineWrapper extends events.EventEmitter {
2923 constructor(document, options) {
2924 super();
2925 this.document = document;
2926 this.indent = options.indent || 0;
2927 this.characterSpacing = options.characterSpacing || 0;
2928 this.wordSpacing = options.wordSpacing === 0;
2929 this.columns = options.columns || 1;
2930 this.columnGap = options.columnGap != null ? options.columnGap : 18; // 1/4 inch
2931 this.lineWidth = (options.width - this.columnGap * (this.columns - 1)) / this.columns;
2932 this.spaceLeft = this.lineWidth;
2933 this.startX = this.document.x;
2934 this.startY = this.document.y;
2935 this.column = 1;
2936 this.ellipsis = options.ellipsis;
2937 this.continuedX = 0;
2938 this.features = options.features;
2939
2940 // calculate the maximum Y position the text can appear at
2941 if (options.height != null) {
2942 this.height = options.height;
2943 this.maxY = this.startY + options.height;
2944 } else {
2945 this.maxY = this.document.page.maxY();
2946 }
2947
2948 // handle paragraph indents
2949 this.on('firstLine', options => {
2950 // if this is the first line of the text segment, and
2951 // we're continuing where we left off, indent that much
2952 // otherwise use the user specified indent option
2953 const indent = this.continuedX || this.indent;
2954 this.document.x += indent;
2955 this.lineWidth -= indent;
2956 return this.once('line', () => {
2957 this.document.x -= indent;
2958 this.lineWidth += indent;
2959 if (options.continued && !this.continuedX) {
2960 this.continuedX = this.indent;
2961 }
2962 if (!options.continued) {
2963 return this.continuedX = 0;
2964 }
2965 });
2966 });
2967
2968 // handle left aligning last lines of paragraphs
2969 this.on('lastLine', options => {
2970 const {
2971 align
2972 } = options;
2973 if (align === 'justify') {
2974 options.align = 'left';
2975 }
2976 this.lastLine = true;
2977 return this.once('line', () => {
2978 this.document.y += options.paragraphGap || 0;
2979 options.align = align;
2980 return this.lastLine = false;
2981 });
2982 });
2983 }
2984 wordWidth(word) {
2985 return this.document.widthOfString(word, this) + this.characterSpacing + this.wordSpacing;
2986 }
2987 canFit(word, w) {
2988 if (word[word.length - 1] != SOFT_HYPHEN) {
2989 return w <= this.spaceLeft;
2990 }
2991 return w + this.wordWidth(HYPHEN) <= this.spaceLeft;
2992 }
2993 eachWord(text, fn) {
2994 // setup a unicode line breaker
2995 let bk;
2996 const breaker = new LineBreaker(text);
2997 let last = null;
2998 const wordWidths = Object.create(null);
2999 while (bk = breaker.nextBreak()) {
3000 var shouldContinue;
3001 let word = text.slice((last != null ? last.position : undefined) || 0, bk.position);
3002 let w = wordWidths[word] != null ? wordWidths[word] : wordWidths[word] = this.wordWidth(word);
3003
3004 // if the word is longer than the whole line, chop it up
3005 // TODO: break by grapheme clusters, not JS string characters
3006 if (w > this.lineWidth + this.continuedX) {
3007 // make some fake break objects
3008 let lbk = last;
3009 const fbk = {};
3010 while (word.length) {
3011 // fit as much of the word as possible into the space we have
3012 var l, mightGrow;
3013 if (w > this.spaceLeft) {
3014 // start our check at the end of our available space - this method is faster than a loop of each character and it resolves
3015 // an issue with long loops when processing massive words, such as a huge number of spaces
3016 l = Math.ceil(this.spaceLeft / (w / word.length));
3017 w = this.wordWidth(word.slice(0, l));
3018 mightGrow = w <= this.spaceLeft && l < word.length;
3019 } else {
3020 l = word.length;
3021 }
3022 let mustShrink = w > this.spaceLeft && l > 0;
3023 // shrink or grow word as necessary after our near-guess above
3024 while (mustShrink || mightGrow) {
3025 if (mustShrink) {
3026 w = this.wordWidth(word.slice(0, --l));
3027 mustShrink = w > this.spaceLeft && l > 0;
3028 } else {
3029 w = this.wordWidth(word.slice(0, ++l));
3030 mustShrink = w > this.spaceLeft && l > 0;
3031 mightGrow = w <= this.spaceLeft && l < word.length;
3032 }
3033 }
3034
3035 // check for the edge case where a single character cannot fit into a line.
3036 if (l === 0 && this.spaceLeft === this.lineWidth) {
3037 l = 1;
3038 }
3039
3040 // send a required break unless this is the last piece and a linebreak is not specified
3041 fbk.required = bk.required || l < word.length;
3042 shouldContinue = fn(word.slice(0, l), w, fbk, lbk);
3043 lbk = {
3044 required: false
3045 };
3046
3047 // get the remaining piece of the word
3048 word = word.slice(l);
3049 w = this.wordWidth(word);
3050 if (shouldContinue === false) {
3051 break;
3052 }
3053 }
3054 } else {
3055 // otherwise just emit the break as it was given to us
3056 shouldContinue = fn(word, w, bk, last);
3057 }
3058 if (shouldContinue === false) {
3059 break;
3060 }
3061 last = bk;
3062 }
3063 }
3064 wrap(text, options) {
3065 // override options from previous continued fragments
3066 if (options.indent != null) {
3067 this.indent = options.indent;
3068 }
3069 if (options.characterSpacing != null) {
3070 this.characterSpacing = options.characterSpacing;
3071 }
3072 if (options.wordSpacing != null) {
3073 this.wordSpacing = options.wordSpacing;
3074 }
3075 if (options.ellipsis != null) {
3076 this.ellipsis = options.ellipsis;
3077 }
3078
3079 // make sure we're actually on the page
3080 // and that the first line of is never by
3081 // itself at the bottom of a page (orphans)
3082 const nextY = this.document.y + this.document.currentLineHeight(true);
3083 if (this.document.y > this.maxY || nextY > this.maxY) {
3084 this.nextSection();
3085 }
3086 let buffer = '';
3087 let textWidth = 0;
3088 let wc = 0;
3089 let lc = 0;
3090 let {
3091 y
3092 } = this.document; // used to reset Y pos if options.continued (below)
3093 const emitLine = () => {
3094 options.textWidth = textWidth + this.wordSpacing * (wc - 1);
3095 options.wordCount = wc;
3096 options.lineWidth = this.lineWidth;
3097 ({
3098 y
3099 } = this.document);
3100 this.emit('line', buffer, options, this);
3101 return lc++;
3102 };
3103 this.emit('sectionStart', options, this);
3104 this.eachWord(text, (word, w, bk, last) => {
3105 if (last == null || last.required) {
3106 this.emit('firstLine', options, this);
3107 this.spaceLeft = this.lineWidth;
3108 }
3109 if (this.canFit(word, w)) {
3110 buffer += word;
3111 textWidth += w;
3112 wc++;
3113 }
3114 if (bk.required || !this.canFit(word, w)) {
3115 // if the user specified a max height and an ellipsis, and is about to pass the
3116 // max height and max columns after the next line, append the ellipsis
3117 const lh = this.document.currentLineHeight(true);
3118 if (this.height != null && this.ellipsis && this.document.y + lh * 2 > this.maxY && this.column >= this.columns) {
3119 if (this.ellipsis === true) {
3120 this.ellipsis = '…';
3121 } // map default ellipsis character
3122 buffer = buffer.replace(/\s+$/, '');
3123 textWidth = this.wordWidth(buffer + this.ellipsis);
3124
3125 // remove characters from the buffer until the ellipsis fits
3126 // to avoid infinite loop need to stop while-loop if buffer is empty string
3127 while (buffer && textWidth > this.lineWidth) {
3128 buffer = buffer.slice(0, -1).replace(/\s+$/, '');
3129 textWidth = this.wordWidth(buffer + this.ellipsis);
3130 }
3131 // need to add ellipsis only if there is enough space for it
3132 if (textWidth <= this.lineWidth) {
3133 buffer = buffer + this.ellipsis;
3134 }
3135 textWidth = this.wordWidth(buffer);
3136 }
3137 if (bk.required) {
3138 if (w > this.spaceLeft) {
3139 emitLine();
3140 buffer = word;
3141 textWidth = w;
3142 wc = 1;
3143 }
3144 this.emit('lastLine', options, this);
3145 }
3146
3147 // Previous entry is a soft hyphen - add visible hyphen.
3148 if (buffer[buffer.length - 1] == SOFT_HYPHEN) {
3149 buffer = buffer.slice(0, -1) + HYPHEN;
3150 this.spaceLeft -= this.wordWidth(HYPHEN);
3151 }
3152 emitLine();
3153
3154 // if we've reached the edge of the page,
3155 // continue on a new page or column
3156 if (this.document.y + lh > this.maxY) {
3157 const shouldContinue = this.nextSection();
3158
3159 // stop if we reached the maximum height
3160 if (!shouldContinue) {
3161 wc = 0;
3162 buffer = '';
3163 return false;
3164 }
3165 }
3166
3167 // reset the space left and buffer
3168 if (bk.required) {
3169 this.spaceLeft = this.lineWidth;
3170 buffer = '';
3171 textWidth = 0;
3172 return wc = 0;
3173 } else {
3174 // reset the space left and buffer
3175 this.spaceLeft = this.lineWidth - w;
3176 buffer = word;
3177 textWidth = w;
3178 return wc = 1;
3179 }
3180 } else {
3181 return this.spaceLeft -= w;
3182 }
3183 });
3184 if (wc > 0) {
3185 this.emit('lastLine', options, this);
3186 emitLine();
3187 }
3188 this.emit('sectionEnd', options, this);
3189
3190 // if the wrap is set to be continued, save the X position
3191 // to start the first line of the next segment at, and reset
3192 // the y position
3193 if (options.continued === true) {
3194 if (lc > 1) {
3195 this.continuedX = 0;
3196 }
3197 this.continuedX += options.textWidth || 0;
3198 return this.document.y = y;
3199 } else {
3200 return this.document.x = this.startX;
3201 }
3202 }
3203 nextSection(options) {
3204 this.emit('sectionEnd', options, this);
3205 if (++this.column > this.columns) {
3206 // if a max height was specified by the user, we're done.
3207 // otherwise, the default is to make a new page at the bottom.
3208 if (this.height != null) {
3209 return false;
3210 }
3211 this.document.continueOnNewPage();
3212 this.column = 1;
3213 this.startY = this.document.page.margins.top;
3214 this.maxY = this.document.page.maxY();
3215 this.document.x = this.startX;
3216 if (this.document._fillColor) {
3217 this.document.fillColor(...this.document._fillColor);
3218 }
3219 this.emit('pageBreak', options, this);
3220 } else {
3221 this.document.x += this.lineWidth + this.columnGap;
3222 this.document.y = this.startY;
3223 this.emit('columnBreak', options, this);
3224 }
3225 this.emit('sectionStart', options, this);
3226 return true;
3227 }
3228}
3229
3230const {
3231 number: number$2
3232} = PDFObject;
3233var TextMixin = {
3234 initText() {
3235 this._line = this._line.bind(this);
3236 // Current coordinates
3237 this.x = 0;
3238 this.y = 0;
3239 return this._lineGap = 0;
3240 },
3241 lineGap(_lineGap) {
3242 this._lineGap = _lineGap;
3243 return this;
3244 },
3245 moveDown(lines) {
3246 if (lines == null) {
3247 lines = 1;
3248 }
3249 this.y += this.currentLineHeight(true) * lines + this._lineGap;
3250 return this;
3251 },
3252 moveUp(lines) {
3253 if (lines == null) {
3254 lines = 1;
3255 }
3256 this.y -= this.currentLineHeight(true) * lines + this._lineGap;
3257 return this;
3258 },
3259 _text(text, x, y, options, lineCallback) {
3260 options = this._initOptions(x, y, options);
3261
3262 // Convert text to a string
3263 text = text == null ? '' : `${text}`;
3264
3265 // if the wordSpacing option is specified, remove multiple consecutive spaces
3266 if (options.wordSpacing) {
3267 text = text.replace(/\s{2,}/g, ' ');
3268 }
3269 const addStructure = () => {
3270 if (options.structParent) {
3271 options.structParent.add(this.struct(options.structType || 'P', [this.markStructureContent(options.structType || 'P')]));
3272 }
3273 };
3274
3275 // word wrapping
3276 if (options.width) {
3277 let wrapper = this._wrapper;
3278 if (!wrapper) {
3279 wrapper = new LineWrapper(this, options);
3280 wrapper.on('line', lineCallback);
3281 wrapper.on('firstLine', addStructure);
3282 }
3283 this._wrapper = options.continued ? wrapper : null;
3284 this._textOptions = options.continued ? options : null;
3285 wrapper.wrap(text, options);
3286
3287 // render paragraphs as single lines
3288 } else {
3289 for (let line of text.split('\n')) {
3290 addStructure();
3291 lineCallback(line, options);
3292 }
3293 }
3294 return this;
3295 },
3296 text(text, x, y, options) {
3297 return this._text(text, x, y, options, this._line);
3298 },
3299 widthOfString(string, options = {}) {
3300 return this._font.widthOfString(string, this._fontSize, options.features) + (options.characterSpacing || 0) * (string.length - 1);
3301 },
3302 heightOfString(text, options) {
3303 const {
3304 x,
3305 y
3306 } = this;
3307 options = this._initOptions(options);
3308 options.height = Infinity; // don't break pages
3309
3310 const lineGap = options.lineGap || this._lineGap || 0;
3311 this._text(text, this.x, this.y, options, () => {
3312 return this.y += this.currentLineHeight(true) + lineGap;
3313 });
3314 const height = this.y - y;
3315 this.x = x;
3316 this.y = y;
3317 return height;
3318 },
3319 list(list, x, y, options, wrapper) {
3320 options = this._initOptions(x, y, options);
3321 const listType = options.listType || 'bullet';
3322 const unit = Math.round(this._font.ascender / 1000 * this._fontSize);
3323 const midLine = unit / 2;
3324 const r = options.bulletRadius || unit / 3;
3325 const indent = options.textIndent || (listType === 'bullet' ? r * 5 : unit * 2);
3326 const itemIndent = options.bulletIndent || (listType === 'bullet' ? r * 8 : unit * 2);
3327 let level = 1;
3328 const items = [];
3329 const levels = [];
3330 const numbers = [];
3331 var flatten = function (list) {
3332 let n = 1;
3333 for (let i = 0; i < list.length; i++) {
3334 const item = list[i];
3335 if (Array.isArray(item)) {
3336 level++;
3337 flatten(item);
3338 level--;
3339 } else {
3340 items.push(item);
3341 levels.push(level);
3342 if (listType !== 'bullet') {
3343 numbers.push(n++);
3344 }
3345 }
3346 }
3347 };
3348 flatten(list);
3349 const label = function (n) {
3350 switch (listType) {
3351 case 'numbered':
3352 return `${n}.`;
3353 case 'lettered':
3354 var letter = String.fromCharCode((n - 1) % 26 + 65);
3355 var times = Math.floor((n - 1) / 26 + 1);
3356 var text = Array(times + 1).join(letter);
3357 return `${text}.`;
3358 }
3359 };
3360 const drawListItem = function (listItem) {
3361 wrapper = new LineWrapper(this, options);
3362 wrapper.on('line', this._line);
3363 level = 1;
3364 let i = 0;
3365 wrapper.once('firstLine', () => {
3366 let item, itemType, labelType, bodyType;
3367 if (options.structParent) {
3368 if (options.structTypes) {
3369 [itemType, labelType, bodyType] = options.structTypes;
3370 } else {
3371 [itemType, labelType, bodyType] = ['LI', 'Lbl', 'LBody'];
3372 }
3373 }
3374 if (itemType) {
3375 item = this.struct(itemType);
3376 options.structParent.add(item);
3377 } else if (options.structParent) {
3378 item = options.structParent;
3379 }
3380 let l;
3381 if ((l = levels[i++]) !== level) {
3382 const diff = itemIndent * (l - level);
3383 this.x += diff;
3384 wrapper.lineWidth -= diff;
3385 level = l;
3386 }
3387 if (item && (labelType || bodyType)) {
3388 item.add(this.struct(labelType || bodyType, [this.markStructureContent(labelType || bodyType)]));
3389 }
3390 switch (listType) {
3391 case 'bullet':
3392 this.circle(this.x - indent + r, this.y + midLine, r);
3393 this.fill();
3394 break;
3395 case 'numbered':
3396 case 'lettered':
3397 var text = label(numbers[i - 1]);
3398 this._fragment(text, this.x - indent, this.y, options);
3399 break;
3400 }
3401 if (item && labelType && bodyType) {
3402 item.add(this.struct(bodyType, [this.markStructureContent(bodyType)]));
3403 }
3404 if (item && item !== options.structParent) {
3405 item.end();
3406 }
3407 });
3408 wrapper.on('sectionStart', () => {
3409 const pos = indent + itemIndent * (level - 1);
3410 this.x += pos;
3411 return wrapper.lineWidth -= pos;
3412 });
3413 wrapper.on('sectionEnd', () => {
3414 const pos = indent + itemIndent * (level - 1);
3415 this.x -= pos;
3416 return wrapper.lineWidth += pos;
3417 });
3418 wrapper.wrap(listItem, options);
3419 };
3420 for (let i = 0; i < items.length; i++) {
3421 drawListItem.call(this, items[i]);
3422 }
3423 return this;
3424 },
3425 _initOptions(x = {}, y, options = {}) {
3426 if (typeof x === 'object') {
3427 options = x;
3428 x = null;
3429 }
3430
3431 // clone options object
3432 const result = Object.assign({}, options);
3433
3434 // extend options with previous values for continued text
3435 if (this._textOptions) {
3436 for (let key in this._textOptions) {
3437 const val = this._textOptions[key];
3438 if (key !== 'continued') {
3439 if (result[key] === undefined) {
3440 result[key] = val;
3441 }
3442 }
3443 }
3444 }
3445
3446 // Update the current position
3447 if (x != null) {
3448 this.x = x;
3449 }
3450 if (y != null) {
3451 this.y = y;
3452 }
3453
3454 // wrap to margins if no x or y position passed
3455 if (result.lineBreak !== false) {
3456 if (result.width == null) {
3457 result.width = this.page.width - this.x - this.page.margins.right;
3458 }
3459 result.width = Math.max(result.width, 0);
3460 }
3461 if (!result.columns) {
3462 result.columns = 0;
3463 }
3464 if (result.columnGap == null) {
3465 result.columnGap = 18;
3466 } // 1/4 inch
3467
3468 return result;
3469 },
3470 _line(text, options = {}, wrapper) {
3471 this._fragment(text, this.x, this.y, options);
3472 const lineGap = options.lineGap || this._lineGap || 0;
3473 if (!wrapper) {
3474 return this.x += this.widthOfString(text);
3475 } else {
3476 return this.y += this.currentLineHeight(true) + lineGap;
3477 }
3478 },
3479 _fragment(text, x, y, options) {
3480 let dy, encoded, i, positions, textWidth, words;
3481 text = `${text}`.replace(/\n/g, '');
3482 if (text.length === 0) {
3483 return;
3484 }
3485
3486 // handle options
3487 const align = options.align || 'left';
3488 let wordSpacing = options.wordSpacing || 0;
3489 const characterSpacing = options.characterSpacing || 0;
3490
3491 // text alignments
3492 if (options.width) {
3493 switch (align) {
3494 case 'right':
3495 textWidth = this.widthOfString(text.replace(/\s+$/, ''), options);
3496 x += options.lineWidth - textWidth;
3497 break;
3498 case 'center':
3499 x += options.lineWidth / 2 - options.textWidth / 2;
3500 break;
3501 case 'justify':
3502 // calculate the word spacing value
3503 words = text.trim().split(/\s+/);
3504 textWidth = this.widthOfString(text.replace(/\s+/g, ''), options);
3505 var spaceWidth = this.widthOfString(' ') + characterSpacing;
3506 wordSpacing = Math.max(0, (options.lineWidth - textWidth) / Math.max(1, words.length - 1) - spaceWidth);
3507 break;
3508 }
3509 }
3510
3511 // text baseline alignments based on http://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling
3512 if (typeof options.baseline === 'number') {
3513 dy = -options.baseline;
3514 } else {
3515 switch (options.baseline) {
3516 case 'svg-middle':
3517 dy = 0.5 * this._font.xHeight;
3518 break;
3519 case 'middle':
3520 case 'svg-central':
3521 dy = 0.5 * (this._font.descender + this._font.ascender);
3522 break;
3523 case 'bottom':
3524 case 'ideographic':
3525 dy = this._font.descender;
3526 break;
3527 case 'alphabetic':
3528 dy = 0;
3529 break;
3530 case 'mathematical':
3531 dy = 0.5 * this._font.ascender;
3532 break;
3533 case 'hanging':
3534 dy = 0.8 * this._font.ascender;
3535 break;
3536 case 'top':
3537 dy = this._font.ascender;
3538 break;
3539 default:
3540 dy = this._font.ascender;
3541 }
3542 dy = dy / 1000 * this._fontSize;
3543 }
3544
3545 // calculate the actual rendered width of the string after word and character spacing
3546 const renderedWidth = options.textWidth + wordSpacing * (options.wordCount - 1) + characterSpacing * (text.length - 1);
3547
3548 // create link annotations if the link option is given
3549 if (options.link != null) {
3550 this.link(x, y, renderedWidth, this.currentLineHeight(), options.link);
3551 }
3552 if (options.goTo != null) {
3553 this.goTo(x, y, renderedWidth, this.currentLineHeight(), options.goTo);
3554 }
3555 if (options.destination != null) {
3556 this.addNamedDestination(options.destination, 'XYZ', x, y, null);
3557 }
3558
3559 // create underline
3560 if (options.underline) {
3561 this.save();
3562 if (!options.stroke) {
3563 this.strokeColor(...(this._fillColor || []));
3564 }
3565 const lineWidth = this._fontSize < 10 ? 0.5 : Math.floor(this._fontSize / 10);
3566 this.lineWidth(lineWidth);
3567 let lineY = y + this.currentLineHeight() - lineWidth;
3568 this.moveTo(x, lineY);
3569 this.lineTo(x + renderedWidth, lineY);
3570 this.stroke();
3571 this.restore();
3572 }
3573
3574 // create strikethrough line
3575 if (options.strike) {
3576 this.save();
3577 if (!options.stroke) {
3578 this.strokeColor(...(this._fillColor || []));
3579 }
3580 const lineWidth = this._fontSize < 10 ? 0.5 : Math.floor(this._fontSize / 10);
3581 this.lineWidth(lineWidth);
3582 let lineY = y + this.currentLineHeight() / 2;
3583 this.moveTo(x, lineY);
3584 this.lineTo(x + renderedWidth, lineY);
3585 this.stroke();
3586 this.restore();
3587 }
3588 this.save();
3589
3590 // oblique (angle in degrees or boolean)
3591 if (options.oblique) {
3592 let skew;
3593 if (typeof options.oblique === 'number') {
3594 skew = -Math.tan(options.oblique * Math.PI / 180);
3595 } else {
3596 skew = -0.25;
3597 }
3598 this.transform(1, 0, 0, 1, x, y);
3599 this.transform(1, 0, skew, 1, -skew * dy, 0);
3600 this.transform(1, 0, 0, 1, -x, -y);
3601 }
3602
3603 // flip coordinate system
3604 this.transform(1, 0, 0, -1, 0, this.page.height);
3605 y = this.page.height - y - dy;
3606
3607 // add current font to page if necessary
3608 if (this.page.fonts[this._font.id] == null) {
3609 this.page.fonts[this._font.id] = this._font.ref();
3610 }
3611
3612 // begin the text object
3613 this.addContent('BT');
3614
3615 // text position
3616 this.addContent(`1 0 0 1 ${number$2(x)} ${number$2(y)} Tm`);
3617
3618 // font and font size
3619 this.addContent(`/${this._font.id} ${number$2(this._fontSize)} Tf`);
3620
3621 // rendering mode
3622 const mode = options.fill && options.stroke ? 2 : options.stroke ? 1 : 0;
3623 if (mode) {
3624 this.addContent(`${mode} Tr`);
3625 }
3626
3627 // Character spacing
3628 if (characterSpacing) {
3629 this.addContent(`${number$2(characterSpacing)} Tc`);
3630 }
3631
3632 // Add the actual text
3633 // If we have a word spacing value, we need to encode each word separately
3634 // since the normal Tw operator only works on character code 32, which isn't
3635 // used for embedded fonts.
3636 if (wordSpacing) {
3637 words = text.trim().split(/\s+/);
3638 wordSpacing += this.widthOfString(' ') + characterSpacing;
3639 wordSpacing *= 1000 / this._fontSize;
3640 encoded = [];
3641 positions = [];
3642 for (let word of words) {
3643 const [encodedWord, positionsWord] = this._font.encode(word, options.features);
3644 encoded = encoded.concat(encodedWord);
3645 positions = positions.concat(positionsWord);
3646
3647 // add the word spacing to the end of the word
3648 // clone object because of cache
3649 const space = {};
3650 const object = positions[positions.length - 1];
3651 for (let key in object) {
3652 const val = object[key];
3653 space[key] = val;
3654 }
3655 space.xAdvance += wordSpacing;
3656 positions[positions.length - 1] = space;
3657 }
3658 } else {
3659 [encoded, positions] = this._font.encode(text, options.features);
3660 }
3661 const scale = this._fontSize / 1000;
3662 const commands = [];
3663 let last = 0;
3664 let hadOffset = false;
3665
3666 // Adds a segment of text to the TJ command buffer
3667 const addSegment = cur => {
3668 if (last < cur) {
3669 const hex = encoded.slice(last, cur).join('');
3670 const advance = positions[cur - 1].xAdvance - positions[cur - 1].advanceWidth;
3671 commands.push(`<${hex}> ${number$2(-advance)}`);
3672 }
3673 return last = cur;
3674 };
3675
3676 // Flushes the current TJ commands to the output stream
3677 const flush = i => {
3678 addSegment(i);
3679 if (commands.length > 0) {
3680 this.addContent(`[${commands.join(' ')}] TJ`);
3681 return commands.length = 0;
3682 }
3683 };
3684 for (i = 0; i < positions.length; i++) {
3685 // If we have an x or y offset, we have to break out of the current TJ command
3686 // so we can move the text position.
3687 const pos = positions[i];
3688 if (pos.xOffset || pos.yOffset) {
3689 // Flush the current buffer
3690 flush(i);
3691
3692 // Move the text position and flush just the current character
3693 this.addContent(`1 0 0 1 ${number$2(x + pos.xOffset * scale)} ${number$2(y + pos.yOffset * scale)} Tm`);
3694 flush(i + 1);
3695 hadOffset = true;
3696 } else {
3697 // If the last character had an offset, reset the text position
3698 if (hadOffset) {
3699 this.addContent(`1 0 0 1 ${number$2(x)} ${number$2(y)} Tm`);
3700 hadOffset = false;
3701 }
3702
3703 // Group segments that don't have any advance adjustments
3704 if (pos.xAdvance - pos.advanceWidth !== 0) {
3705 addSegment(i + 1);
3706 }
3707 }
3708 x += pos.xAdvance * scale;
3709 }
3710
3711 // Flush any remaining commands
3712 flush(i);
3713
3714 // end the text object
3715 this.addContent('ET');
3716
3717 // restore flipped coordinate system
3718 return this.restore();
3719 }
3720};
3721
3722const MARKERS = [0xffc0, 0xffc1, 0xffc2, 0xffc3, 0xffc5, 0xffc6, 0xffc7, 0xffc8, 0xffc9, 0xffca, 0xffcb, 0xffcc, 0xffcd, 0xffce, 0xffcf];
3723const COLOR_SPACE_MAP = {
3724 1: 'DeviceGray',
3725 3: 'DeviceRGB',
3726 4: 'DeviceCMYK'
3727};
3728class JPEG {
3729 constructor(data, label) {
3730 let marker;
3731 this.data = data;
3732 this.label = label;
3733 if (this.data.readUInt16BE(0) !== 0xffd8) {
3734 throw 'SOI not found in JPEG';
3735 }
3736
3737 // Parse the EXIF orientation
3738 this.orientation = exif.fromBuffer(this.data).Orientation || 1;
3739 let pos = 2;
3740 while (pos < this.data.length) {
3741 marker = this.data.readUInt16BE(pos);
3742 pos += 2;
3743 if (MARKERS.includes(marker)) {
3744 break;
3745 }
3746 pos += this.data.readUInt16BE(pos);
3747 }
3748 if (!MARKERS.includes(marker)) {
3749 throw 'Invalid JPEG.';
3750 }
3751 pos += 2;
3752 this.bits = this.data[pos++];
3753 this.height = this.data.readUInt16BE(pos);
3754 pos += 2;
3755 this.width = this.data.readUInt16BE(pos);
3756 pos += 2;
3757 const channels = this.data[pos++];
3758 this.colorSpace = COLOR_SPACE_MAP[channels];
3759 this.obj = null;
3760 }
3761 embed(document) {
3762 if (this.obj) {
3763 return;
3764 }
3765 this.obj = document.ref({
3766 Type: 'XObject',
3767 Subtype: 'Image',
3768 BitsPerComponent: this.bits,
3769 Width: this.width,
3770 Height: this.height,
3771 ColorSpace: this.colorSpace,
3772 Filter: 'DCTDecode'
3773 });
3774
3775 // add extra decode params for CMYK images. By swapping the
3776 // min and max values from the default, we invert the colors. See
3777 // section 4.8.4 of the spec.
3778 if (this.colorSpace === 'DeviceCMYK') {
3779 this.obj.data['Decode'] = [1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0];
3780 }
3781 this.obj.end(this.data);
3782
3783 // free memory
3784 return this.data = null;
3785 }
3786}
3787
3788class PNGImage {
3789 constructor(data, label) {
3790 this.label = label;
3791 this.image = new PNG(data);
3792 this.width = this.image.width;
3793 this.height = this.image.height;
3794 this.imgData = this.image.imgData;
3795 this.obj = null;
3796 }
3797 embed(document) {
3798 let dataDecoded = false;
3799 this.document = document;
3800 if (this.obj) {
3801 return;
3802 }
3803 const hasAlphaChannel = this.image.hasAlphaChannel;
3804 const isInterlaced = this.image.interlaceMethod === 1;
3805 this.obj = this.document.ref({
3806 Type: 'XObject',
3807 Subtype: 'Image',
3808 BitsPerComponent: hasAlphaChannel ? 8 : this.image.bits,
3809 Width: this.width,
3810 Height: this.height,
3811 Filter: 'FlateDecode'
3812 });
3813 if (!hasAlphaChannel) {
3814 const params = this.document.ref({
3815 Predictor: isInterlaced ? 1 : 15,
3816 Colors: this.image.colors,
3817 BitsPerComponent: this.image.bits,
3818 Columns: this.width
3819 });
3820 this.obj.data['DecodeParms'] = params;
3821 params.end();
3822 }
3823 if (this.image.palette.length === 0) {
3824 this.obj.data['ColorSpace'] = this.image.colorSpace;
3825 } else {
3826 // embed the color palette in the PDF as an object stream
3827 const palette = this.document.ref();
3828 palette.end(Buffer.from(this.image.palette));
3829
3830 // build the color space array for the image
3831 this.obj.data['ColorSpace'] = ['Indexed', 'DeviceRGB', this.image.palette.length / 3 - 1, palette];
3832 }
3833
3834 // For PNG color types 0, 2 and 3, the transparency data is stored in
3835 // a dedicated PNG chunk.
3836 if (this.image.transparency.grayscale != null) {
3837 // Use Color Key Masking (spec section 4.8.5)
3838 // An array with N elements, where N is two times the number of color components.
3839 const val = this.image.transparency.grayscale;
3840 this.obj.data['Mask'] = [val, val];
3841 } else if (this.image.transparency.rgb) {
3842 // Use Color Key Masking (spec section 4.8.5)
3843 // An array with N elements, where N is two times the number of color components.
3844 const {
3845 rgb
3846 } = this.image.transparency;
3847 const mask = [];
3848 for (let x of rgb) {
3849 mask.push(x, x);
3850 }
3851 this.obj.data['Mask'] = mask;
3852 } else if (this.image.transparency.indexed) {
3853 // Create a transparency SMask for the image based on the data
3854 // in the PLTE and tRNS sections. See below for details on SMasks.
3855 dataDecoded = true;
3856 return this.loadIndexedAlphaChannel();
3857 } else if (hasAlphaChannel) {
3858 // For PNG color types 4 and 6, the transparency data is stored as a alpha
3859 // channel mixed in with the main image data. Separate this data out into an
3860 // SMask object and store it separately in the PDF.
3861 dataDecoded = true;
3862 return this.splitAlphaChannel();
3863 }
3864 if (isInterlaced && !dataDecoded) {
3865 return this.decodeData();
3866 }
3867 this.finalize();
3868 }
3869 finalize() {
3870 if (this.alphaChannel) {
3871 const sMask = this.document.ref({
3872 Type: 'XObject',
3873 Subtype: 'Image',
3874 Height: this.height,
3875 Width: this.width,
3876 BitsPerComponent: 8,
3877 Filter: 'FlateDecode',
3878 ColorSpace: 'DeviceGray',
3879 Decode: [0, 1]
3880 });
3881 sMask.end(this.alphaChannel);
3882 this.obj.data['SMask'] = sMask;
3883 }
3884
3885 // add the actual image data
3886 this.obj.end(this.imgData);
3887
3888 // free memory
3889 this.image = null;
3890 return this.imgData = null;
3891 }
3892 splitAlphaChannel() {
3893 return this.image.decodePixels(pixels => {
3894 let a, p;
3895 const colorCount = this.image.colors;
3896 const pixelCount = this.width * this.height;
3897 const imgData = Buffer.alloc(pixelCount * colorCount);
3898 const alphaChannel = Buffer.alloc(pixelCount);
3899 let i = p = a = 0;
3900 const len = pixels.length;
3901 // For 16bit images copy only most significant byte (MSB) - PNG data is always stored in network byte order (MSB first)
3902 const skipByteCount = this.image.bits === 16 ? 1 : 0;
3903 while (i < len) {
3904 for (let colorIndex = 0; colorIndex < colorCount; colorIndex++) {
3905 imgData[p++] = pixels[i++];
3906 i += skipByteCount;
3907 }
3908 alphaChannel[a++] = pixels[i++];
3909 i += skipByteCount;
3910 }
3911 this.imgData = zlib.deflateSync(imgData);
3912 this.alphaChannel = zlib.deflateSync(alphaChannel);
3913 return this.finalize();
3914 });
3915 }
3916 loadIndexedAlphaChannel() {
3917 const transparency = this.image.transparency.indexed;
3918 return this.image.decodePixels(pixels => {
3919 const alphaChannel = Buffer.alloc(this.width * this.height);
3920 let i = 0;
3921 for (let j = 0, end = pixels.length; j < end; j++) {
3922 alphaChannel[i++] = transparency[pixels[j]];
3923 }
3924 this.alphaChannel = zlib.deflateSync(alphaChannel);
3925 return this.finalize();
3926 });
3927 }
3928 decodeData() {
3929 this.image.decodePixels(pixels => {
3930 this.imgData = zlib.deflateSync(pixels);
3931 this.finalize();
3932 });
3933 }
3934}
3935
3936/*
3937PDFImage - embeds images in PDF documents
3938By Devon Govett
3939*/
3940class PDFImage {
3941 static open(src, label) {
3942 let data;
3943 if (Buffer.isBuffer(src)) {
3944 data = src;
3945 } else if (src instanceof ArrayBuffer) {
3946 data = Buffer.from(new Uint8Array(src));
3947 } else {
3948 let match;
3949 if (match = /^data:.+?;base64,(.*)$/.exec(src)) {
3950 data = Buffer.from(match[1], 'base64');
3951 } else {
3952 data = fs.readFileSync(src);
3953 if (!data) {
3954 return;
3955 }
3956 }
3957 }
3958 if (data[0] === 0xff && data[1] === 0xd8) {
3959 return new JPEG(data, label);
3960 } else if (data[0] === 0x89 && data.toString('ascii', 1, 4) === 'PNG') {
3961 return new PNGImage(data, label);
3962 } else {
3963 throw new Error('Unknown image format.');
3964 }
3965 }
3966}
3967
3968var ImagesMixin = {
3969 initImages() {
3970 this._imageRegistry = {};
3971 return this._imageCount = 0;
3972 },
3973 image(src, x, y, options = {}) {
3974 let bh, bp, bw, image, ip, left, left1, rotateAngle, originX, originY;
3975 if (typeof x === 'object') {
3976 options = x;
3977 x = null;
3978 }
3979
3980 // Ignore orientation based on document options or image options
3981 const ignoreOrientation = options.ignoreOrientation || options.ignoreOrientation !== false && this.options.ignoreOrientation;
3982 x = (left = x != null ? x : options.x) != null ? left : this.x;
3983 y = (left1 = y != null ? y : options.y) != null ? left1 : this.y;
3984 if (typeof src === 'string') {
3985 image = this._imageRegistry[src];
3986 }
3987 if (!image) {
3988 if (src.width && src.height) {
3989 image = src;
3990 } else {
3991 image = this.openImage(src);
3992 }
3993 }
3994 if (!image.obj) {
3995 image.embed(this);
3996 }
3997 if (this.page.xobjects[image.label] == null) {
3998 this.page.xobjects[image.label] = image.obj;
3999 }
4000 let {
4001 width,
4002 height
4003 } = image;
4004
4005 // If EXIF orientation calls for it, swap width and height
4006 if (!ignoreOrientation && image.orientation > 4) {
4007 [width, height] = [height, width];
4008 }
4009 let w = options.width || width;
4010 let h = options.height || height;
4011 if (options.width && !options.height) {
4012 const wp = w / width;
4013 w = width * wp;
4014 h = height * wp;
4015 } else if (options.height && !options.width) {
4016 const hp = h / height;
4017 w = width * hp;
4018 h = height * hp;
4019 } else if (options.scale) {
4020 w = width * options.scale;
4021 h = height * options.scale;
4022 } else if (options.fit) {
4023 [bw, bh] = options.fit;
4024 bp = bw / bh;
4025 ip = width / height;
4026 if (ip > bp) {
4027 w = bw;
4028 h = bw / ip;
4029 } else {
4030 h = bh;
4031 w = bh * ip;
4032 }
4033 } else if (options.cover) {
4034 [bw, bh] = options.cover;
4035 bp = bw / bh;
4036 ip = width / height;
4037 if (ip > bp) {
4038 h = bh;
4039 w = bh * ip;
4040 } else {
4041 w = bw;
4042 h = bw / ip;
4043 }
4044 }
4045 if (options.fit || options.cover) {
4046 if (options.align === 'center') {
4047 x = x + bw / 2 - w / 2;
4048 } else if (options.align === 'right') {
4049 x = x + bw - w;
4050 }
4051 if (options.valign === 'center') {
4052 y = y + bh / 2 - h / 2;
4053 } else if (options.valign === 'bottom') {
4054 y = y + bh - h;
4055 }
4056 }
4057 if (!ignoreOrientation) {
4058 switch (image.orientation) {
4059 // No orientation (need to flip image, though, because of the default transform matrix on the document)
4060 default:
4061 case 1:
4062 h = -h;
4063 y -= h;
4064 rotateAngle = 0;
4065 break;
4066 // Flip Horizontal
4067 case 2:
4068 w = -w;
4069 h = -h;
4070 x -= w;
4071 y -= h;
4072 rotateAngle = 0;
4073 break;
4074 // Rotate 180 degrees
4075 case 3:
4076 originX = x;
4077 originY = y;
4078 h = -h;
4079 x -= w;
4080 rotateAngle = 180;
4081 break;
4082 // Flip vertical
4083 case 4:
4084 // Do nothing, image will be flipped
4085
4086 break;
4087 // Flip horizontally and rotate 270 degrees CW
4088 case 5:
4089 originX = x;
4090 originY = y;
4091 [w, h] = [h, w];
4092 y -= h;
4093 rotateAngle = 90;
4094 break;
4095 // Rotate 90 degrees CW
4096 case 6:
4097 originX = x;
4098 originY = y;
4099 [w, h] = [h, w];
4100 h = -h;
4101 rotateAngle = 90;
4102 break;
4103 // Flip horizontally and rotate 90 degrees CW
4104 case 7:
4105 originX = x;
4106 originY = y;
4107 [w, h] = [h, w];
4108 h = -h;
4109 w = -w;
4110 x -= w;
4111 rotateAngle = 90;
4112 break;
4113 // Rotate 270 degrees CW
4114 case 8:
4115 originX = x;
4116 originY = y;
4117 [w, h] = [h, w];
4118 h = -h;
4119 x -= w;
4120 y -= h;
4121 rotateAngle = -90;
4122 break;
4123 }
4124 } else {
4125 h = -h;
4126 y -= h;
4127 rotateAngle = 0;
4128 }
4129
4130 // create link annotations if the link option is given
4131 if (options.link != null) {
4132 this.link(x, y, w, h, options.link);
4133 }
4134 if (options.goTo != null) {
4135 this.goTo(x, y, w, h, options.goTo);
4136 }
4137 if (options.destination != null) {
4138 this.addNamedDestination(options.destination, 'XYZ', x, y, null);
4139 }
4140
4141 // Set the current y position to below the image if it is in the document flow
4142 if (this.y === y) {
4143 this.y += h;
4144 }
4145 this.save();
4146 if (rotateAngle) {
4147 this.rotate(rotateAngle, {
4148 origin: [originX, originY]
4149 });
4150 }
4151 this.transform(w, 0, 0, h, x, y);
4152 this.addContent(`/${image.label} Do`);
4153 this.restore();
4154 return this;
4155 },
4156 openImage(src) {
4157 let image;
4158 if (typeof src === 'string') {
4159 image = this._imageRegistry[src];
4160 }
4161 if (!image) {
4162 image = PDFImage.open(src, `I${++this._imageCount}`);
4163 if (typeof src === 'string') {
4164 this._imageRegistry[src] = image;
4165 }
4166 }
4167 return image;
4168 }
4169};
4170
4171var AnnotationsMixin = {
4172 annotate(x, y, w, h, options) {
4173 options.Type = 'Annot';
4174 options.Rect = this._convertRect(x, y, w, h);
4175 options.Border = [0, 0, 0];
4176 if (options.Subtype === 'Link' && typeof options.F === 'undefined') {
4177 options.F = 1 << 2; // Print Annotation Flag
4178 }
4179 if (options.Subtype !== 'Link') {
4180 if (options.C == null) {
4181 options.C = this._normalizeColor(options.color || [0, 0, 0]);
4182 }
4183 } // convert colors
4184 delete options.color;
4185 if (typeof options.Dest === 'string') {
4186 options.Dest = new String(options.Dest);
4187 }
4188
4189 // Capitalize keys
4190 for (let key in options) {
4191 const val = options[key];
4192 options[key[0].toUpperCase() + key.slice(1)] = val;
4193 }
4194 const ref = this.ref(options);
4195 this.page.annotations.push(ref);
4196 ref.end();
4197 return this;
4198 },
4199 note(x, y, w, h, contents, options = {}) {
4200 options.Subtype = 'Text';
4201 options.Contents = new String(contents);
4202 options.Name = 'Comment';
4203 if (options.color == null) {
4204 options.color = [243, 223, 92];
4205 }
4206 return this.annotate(x, y, w, h, options);
4207 },
4208 goTo(x, y, w, h, name, options = {}) {
4209 options.Subtype = 'Link';
4210 options.A = this.ref({
4211 S: 'GoTo',
4212 D: new String(name)
4213 });
4214 options.A.end();
4215 return this.annotate(x, y, w, h, options);
4216 },
4217 link(x, y, w, h, url, options = {}) {
4218 options.Subtype = 'Link';
4219 if (typeof url === 'number') {
4220 // Link to a page in the document (the page must already exist)
4221 const pages = this._root.data.Pages.data;
4222 if (url >= 0 && url < pages.Kids.length) {
4223 options.A = this.ref({
4224 S: 'GoTo',
4225 D: [pages.Kids[url], 'XYZ', null, null, null]
4226 });
4227 options.A.end();
4228 } else {
4229 throw new Error(`The document has no page ${url}`);
4230 }
4231 } else {
4232 // Link to an external url
4233 options.A = this.ref({
4234 S: 'URI',
4235 URI: new String(url)
4236 });
4237 options.A.end();
4238 }
4239 return this.annotate(x, y, w, h, options);
4240 },
4241 _markup(x, y, w, h, options = {}) {
4242 const [x1, y1, x2, y2] = this._convertRect(x, y, w, h);
4243 options.QuadPoints = [x1, y2, x2, y2, x1, y1, x2, y1];
4244 options.Contents = new String();
4245 return this.annotate(x, y, w, h, options);
4246 },
4247 highlight(x, y, w, h, options = {}) {
4248 options.Subtype = 'Highlight';
4249 if (options.color == null) {
4250 options.color = [241, 238, 148];
4251 }
4252 return this._markup(x, y, w, h, options);
4253 },
4254 underline(x, y, w, h, options = {}) {
4255 options.Subtype = 'Underline';
4256 return this._markup(x, y, w, h, options);
4257 },
4258 strike(x, y, w, h, options = {}) {
4259 options.Subtype = 'StrikeOut';
4260 return this._markup(x, y, w, h, options);
4261 },
4262 lineAnnotation(x1, y1, x2, y2, options = {}) {
4263 options.Subtype = 'Line';
4264 options.Contents = new String();
4265 options.L = [x1, this.page.height - y1, x2, this.page.height - y2];
4266 return this.annotate(x1, y1, x2, y2, options);
4267 },
4268 rectAnnotation(x, y, w, h, options = {}) {
4269 options.Subtype = 'Square';
4270 options.Contents = new String();
4271 return this.annotate(x, y, w, h, options);
4272 },
4273 ellipseAnnotation(x, y, w, h, options = {}) {
4274 options.Subtype = 'Circle';
4275 options.Contents = new String();
4276 return this.annotate(x, y, w, h, options);
4277 },
4278 textAnnotation(x, y, w, h, text, options = {}) {
4279 options.Subtype = 'FreeText';
4280 options.Contents = new String(text);
4281 options.DA = new String();
4282 return this.annotate(x, y, w, h, options);
4283 },
4284 fileAnnotation(x, y, w, h, file = {}, options = {}) {
4285 // create hidden file
4286 const filespec = this.file(file.src, Object.assign({
4287 hidden: true
4288 }, file));
4289 options.Subtype = 'FileAttachment';
4290 options.FS = filespec;
4291
4292 // add description from filespec unless description (Contents) has already been set
4293 if (options.Contents) {
4294 options.Contents = new String(options.Contents);
4295 } else if (filespec.data.Desc) {
4296 options.Contents = filespec.data.Desc;
4297 }
4298 return this.annotate(x, y, w, h, options);
4299 },
4300 _convertRect(x1, y1, w, h) {
4301 // flip y1 and y2
4302 let y2 = y1;
4303 y1 += h;
4304
4305 // make x2
4306 let x2 = x1 + w;
4307
4308 // apply current transformation matrix to points
4309 const [m0, m1, m2, m3, m4, m5] = this._ctm;
4310 x1 = m0 * x1 + m2 * y1 + m4;
4311 y1 = m1 * x1 + m3 * y1 + m5;
4312 x2 = m0 * x2 + m2 * y2 + m4;
4313 y2 = m1 * x2 + m3 * y2 + m5;
4314 return [x1, y1, x2, y2];
4315 }
4316};
4317
4318class PDFOutline {
4319 constructor(document, parent, title, dest, options = {
4320 expanded: false
4321 }) {
4322 this.document = document;
4323 this.options = options;
4324 this.outlineData = {};
4325 if (dest !== null) {
4326 this.outlineData['Dest'] = [dest.dictionary, 'Fit'];
4327 }
4328 if (parent !== null) {
4329 this.outlineData['Parent'] = parent;
4330 }
4331 if (title !== null) {
4332 this.outlineData['Title'] = new String(title);
4333 }
4334 this.dictionary = this.document.ref(this.outlineData);
4335 this.children = [];
4336 }
4337 addItem(title, options = {
4338 expanded: false
4339 }) {
4340 const result = new PDFOutline(this.document, this.dictionary, title, this.document.page, options);
4341 this.children.push(result);
4342 return result;
4343 }
4344 endOutline() {
4345 if (this.children.length > 0) {
4346 if (this.options.expanded) {
4347 this.outlineData.Count = this.children.length;
4348 }
4349 const first = this.children[0],
4350 last = this.children[this.children.length - 1];
4351 this.outlineData.First = first.dictionary;
4352 this.outlineData.Last = last.dictionary;
4353 for (let i = 0, len = this.children.length; i < len; i++) {
4354 const child = this.children[i];
4355 if (i > 0) {
4356 child.outlineData.Prev = this.children[i - 1].dictionary;
4357 }
4358 if (i < this.children.length - 1) {
4359 child.outlineData.Next = this.children[i + 1].dictionary;
4360 }
4361 child.endOutline();
4362 }
4363 }
4364 return this.dictionary.end();
4365 }
4366}
4367
4368var OutlineMixin = {
4369 initOutline() {
4370 return this.outline = new PDFOutline(this, null, null, null);
4371 },
4372 endOutline() {
4373 this.outline.endOutline();
4374 if (this.outline.children.length > 0) {
4375 this._root.data.Outlines = this.outline.dictionary;
4376 return this._root.data.PageMode = 'UseOutlines';
4377 }
4378 }
4379};
4380
4381/*
4382PDFStructureContent - a reference to a marked structure content
4383By Ben Schmidt
4384*/
4385
4386class PDFStructureContent {
4387 constructor(pageRef, mcid) {
4388 this.refs = [{
4389 pageRef,
4390 mcid
4391 }];
4392 }
4393 push(structContent) {
4394 structContent.refs.forEach(ref => this.refs.push(ref));
4395 }
4396}
4397
4398/*
4399PDFStructureElement - represents an element in the PDF logical structure tree
4400By Ben Schmidt
4401*/
4402class PDFStructureElement {
4403 constructor(document, type, options = {}, children = null) {
4404 this.document = document;
4405 this._attached = false;
4406 this._ended = false;
4407 this._flushed = false;
4408 this.dictionary = document.ref({
4409 // Type: "StructElem",
4410 S: type
4411 });
4412 const data = this.dictionary.data;
4413 if (Array.isArray(options) || this._isValidChild(options)) {
4414 children = options;
4415 options = {};
4416 }
4417 if (typeof options.title !== 'undefined') {
4418 data.T = new String(options.title);
4419 }
4420 if (typeof options.lang !== 'undefined') {
4421 data.Lang = new String(options.lang);
4422 }
4423 if (typeof options.alt !== 'undefined') {
4424 data.Alt = new String(options.alt);
4425 }
4426 if (typeof options.expanded !== 'undefined') {
4427 data.E = new String(options.expanded);
4428 }
4429 if (typeof options.actual !== 'undefined') {
4430 data.ActualText = new String(options.actual);
4431 }
4432 this._children = [];
4433 if (children) {
4434 if (!Array.isArray(children)) {
4435 children = [children];
4436 }
4437 children.forEach(child => this.add(child));
4438 this.end();
4439 }
4440 }
4441 add(child) {
4442 if (this._ended) {
4443 throw new Error(`Cannot add child to already-ended structure element`);
4444 }
4445 if (!this._isValidChild(child)) {
4446 throw new Error(`Invalid structure element child`);
4447 }
4448 if (child instanceof PDFStructureElement) {
4449 child.setParent(this.dictionary);
4450 if (this._attached) {
4451 child.setAttached();
4452 }
4453 }
4454 if (child instanceof PDFStructureContent) {
4455 this._addContentToParentTree(child);
4456 }
4457 if (typeof child === 'function' && this._attached) {
4458 // _contentForClosure() adds the content to the parent tree
4459 child = this._contentForClosure(child);
4460 }
4461 this._children.push(child);
4462 return this;
4463 }
4464 _addContentToParentTree(content) {
4465 content.refs.forEach(({
4466 pageRef,
4467 mcid
4468 }) => {
4469 const pageStructParents = this.document.getStructParentTree().get(pageRef.data.StructParents);
4470 pageStructParents[mcid] = this.dictionary;
4471 });
4472 }
4473 setParent(parentRef) {
4474 if (this.dictionary.data.P) {
4475 throw new Error(`Structure element added to more than one parent`);
4476 }
4477 this.dictionary.data.P = parentRef;
4478 this._flush();
4479 }
4480 setAttached() {
4481 if (this._attached) {
4482 return;
4483 }
4484 this._children.forEach((child, index) => {
4485 if (child instanceof PDFStructureElement) {
4486 child.setAttached();
4487 }
4488 if (typeof child === 'function') {
4489 this._children[index] = this._contentForClosure(child);
4490 }
4491 });
4492 this._attached = true;
4493 this._flush();
4494 }
4495 end() {
4496 if (this._ended) {
4497 return;
4498 }
4499 this._children.filter(child => child instanceof PDFStructureElement).forEach(child => child.end());
4500 this._ended = true;
4501 this._flush();
4502 }
4503 _isValidChild(child) {
4504 return child instanceof PDFStructureElement || child instanceof PDFStructureContent || typeof child === 'function';
4505 }
4506 _contentForClosure(closure) {
4507 const content = this.document.markStructureContent(this.dictionary.data.S);
4508 closure();
4509 this.document.endMarkedContent();
4510 this._addContentToParentTree(content);
4511 return content;
4512 }
4513 _isFlushable() {
4514 if (!this.dictionary.data.P || !this._ended) {
4515 return false;
4516 }
4517 return this._children.every(child => {
4518 if (typeof child === 'function') {
4519 return false;
4520 }
4521 if (child instanceof PDFStructureElement) {
4522 return child._isFlushable();
4523 }
4524 return true;
4525 });
4526 }
4527 _flush() {
4528 if (this._flushed || !this._isFlushable()) {
4529 return;
4530 }
4531 this.dictionary.data.K = [];
4532 this._children.forEach(child => this._flushChild(child));
4533 this.dictionary.end();
4534
4535 // free memory used by children; the dictionary itself may still be
4536 // referenced by a parent structure element or root, but we can
4537 // at least trim the tree here
4538 this._children = [];
4539 this.dictionary.data.K = null;
4540 this._flushed = true;
4541 }
4542 _flushChild(child) {
4543 if (child instanceof PDFStructureElement) {
4544 this.dictionary.data.K.push(child.dictionary);
4545 }
4546 if (child instanceof PDFStructureContent) {
4547 child.refs.forEach(({
4548 pageRef,
4549 mcid
4550 }) => {
4551 if (!this.dictionary.data.Pg) {
4552 this.dictionary.data.Pg = pageRef;
4553 }
4554 if (this.dictionary.data.Pg === pageRef) {
4555 this.dictionary.data.K.push(mcid);
4556 } else {
4557 this.dictionary.data.K.push({
4558 Type: "MCR",
4559 Pg: pageRef,
4560 MCID: mcid
4561 });
4562 }
4563 });
4564 }
4565 }
4566}
4567
4568/*
4569PDFNumberTree - represents a number tree object
4570*/
4571class PDFNumberTree extends PDFTree {
4572 _compareKeys(a, b) {
4573 return parseInt(a) - parseInt(b);
4574 }
4575 _keysName() {
4576 return "Nums";
4577 }
4578 _dataForKey(k) {
4579 return parseInt(k);
4580 }
4581}
4582
4583/*
4584Markings mixin - support marked content sequences in content streams
4585By Ben Schmidt
4586*/
4587var MarkingsMixin = {
4588 initMarkings(options) {
4589 this.structChildren = [];
4590 if (options.tagged) {
4591 this.getMarkInfoDictionary().data.Marked = true;
4592 this.getStructTreeRoot();
4593 }
4594 },
4595 markContent(tag, options = null) {
4596 if (tag === 'Artifact' || options && options.mcid) {
4597 let toClose = 0;
4598 this.page.markings.forEach(marking => {
4599 if (toClose || marking.structContent || marking.tag === 'Artifact') {
4600 toClose++;
4601 }
4602 });
4603 while (toClose--) {
4604 this.endMarkedContent();
4605 }
4606 }
4607 if (!options) {
4608 this.page.markings.push({
4609 tag
4610 });
4611 this.addContent(`/${tag} BMC`);
4612 return this;
4613 }
4614 this.page.markings.push({
4615 tag,
4616 options
4617 });
4618 const dictionary = {};
4619 if (typeof options.mcid !== 'undefined') {
4620 dictionary.MCID = options.mcid;
4621 }
4622 if (tag === 'Artifact') {
4623 if (typeof options.type === 'string') {
4624 dictionary.Type = options.type;
4625 }
4626 if (Array.isArray(options.bbox)) {
4627 dictionary.BBox = [options.bbox[0], this.page.height - options.bbox[3], options.bbox[2], this.page.height - options.bbox[1]];
4628 }
4629 if (Array.isArray(options.attached) && options.attached.every(val => typeof val === 'string')) {
4630 dictionary.Attached = options.attached;
4631 }
4632 }
4633 if (tag === 'Span') {
4634 if (options.lang) {
4635 dictionary.Lang = new String(options.lang);
4636 }
4637 if (options.alt) {
4638 dictionary.Alt = new String(options.alt);
4639 }
4640 if (options.expanded) {
4641 dictionary.E = new String(options.expanded);
4642 }
4643 if (options.actual) {
4644 dictionary.ActualText = new String(options.actual);
4645 }
4646 }
4647 this.addContent(`/${tag} ${PDFObject.convert(dictionary)} BDC`);
4648 return this;
4649 },
4650 markStructureContent(tag, options = {}) {
4651 const pageStructParents = this.getStructParentTree().get(this.page.structParentTreeKey);
4652 const mcid = pageStructParents.length;
4653 pageStructParents.push(null);
4654 this.markContent(tag, {
4655 ...options,
4656 mcid
4657 });
4658 const structContent = new PDFStructureContent(this.page.dictionary, mcid);
4659 this.page.markings.slice(-1)[0].structContent = structContent;
4660 return structContent;
4661 },
4662 endMarkedContent() {
4663 this.page.markings.pop();
4664 this.addContent('EMC');
4665 return this;
4666 },
4667 struct(type, options = {}, children = null) {
4668 return new PDFStructureElement(this, type, options, children);
4669 },
4670 addStructure(structElem) {
4671 const structTreeRoot = this.getStructTreeRoot();
4672 structElem.setParent(structTreeRoot);
4673 structElem.setAttached();
4674 this.structChildren.push(structElem);
4675 if (!structTreeRoot.data.K) {
4676 structTreeRoot.data.K = [];
4677 }
4678 structTreeRoot.data.K.push(structElem.dictionary);
4679 return this;
4680 },
4681 initPageMarkings(pageMarkings) {
4682 pageMarkings.forEach(marking => {
4683 if (marking.structContent) {
4684 const structContent = marking.structContent;
4685 const newStructContent = this.markStructureContent(marking.tag, marking.options);
4686 structContent.push(newStructContent);
4687 this.page.markings.slice(-1)[0].structContent = structContent;
4688 } else {
4689 this.markContent(marking.tag, marking.options);
4690 }
4691 });
4692 },
4693 endPageMarkings(page) {
4694 const pageMarkings = page.markings;
4695 pageMarkings.forEach(() => page.write('EMC'));
4696 page.markings = [];
4697 return pageMarkings;
4698 },
4699 getMarkInfoDictionary() {
4700 if (!this._root.data.MarkInfo) {
4701 this._root.data.MarkInfo = this.ref({});
4702 }
4703 return this._root.data.MarkInfo;
4704 },
4705 getStructTreeRoot() {
4706 if (!this._root.data.StructTreeRoot) {
4707 this._root.data.StructTreeRoot = this.ref({
4708 Type: 'StructTreeRoot',
4709 ParentTree: new PDFNumberTree(),
4710 ParentTreeNextKey: 0
4711 });
4712 }
4713 return this._root.data.StructTreeRoot;
4714 },
4715 getStructParentTree() {
4716 return this.getStructTreeRoot().data.ParentTree;
4717 },
4718 createStructParentTreeNextKey() {
4719 // initialise the MarkInfo dictionary
4720 this.getMarkInfoDictionary();
4721 const structTreeRoot = this.getStructTreeRoot();
4722 const key = structTreeRoot.data.ParentTreeNextKey++;
4723 structTreeRoot.data.ParentTree.add(key, []);
4724 return key;
4725 },
4726 endMarkings() {
4727 const structTreeRoot = this._root.data.StructTreeRoot;
4728 if (structTreeRoot) {
4729 structTreeRoot.end();
4730 this.structChildren.forEach(structElem => structElem.end());
4731 }
4732 if (this._root.data.MarkInfo) {
4733 this._root.data.MarkInfo.end();
4734 }
4735 }
4736};
4737
4738const FIELD_FLAGS = {
4739 readOnly: 1,
4740 required: 2,
4741 noExport: 4,
4742 multiline: 0x1000,
4743 password: 0x2000,
4744 toggleToOffButton: 0x4000,
4745 radioButton: 0x8000,
4746 pushButton: 0x10000,
4747 combo: 0x20000,
4748 edit: 0x40000,
4749 sort: 0x80000,
4750 multiSelect: 0x200000,
4751 noSpell: 0x400000
4752};
4753const FIELD_JUSTIFY = {
4754 left: 0,
4755 center: 1,
4756 right: 2
4757};
4758const VALUE_MAP = {
4759 value: 'V',
4760 defaultValue: 'DV'
4761};
4762const FORMAT_SPECIAL = {
4763 zip: '0',
4764 zipPlus4: '1',
4765 zip4: '1',
4766 phone: '2',
4767 ssn: '3'
4768};
4769const FORMAT_DEFAULT = {
4770 number: {
4771 nDec: 0,
4772 sepComma: false,
4773 negStyle: 'MinusBlack',
4774 currency: '',
4775 currencyPrepend: true
4776 },
4777 percent: {
4778 nDec: 0,
4779 sepComma: false
4780 }
4781};
4782var AcroFormMixin = {
4783 /**
4784 * Must call if adding AcroForms to a document. Must also call font() before
4785 * this method to set the default font.
4786 */
4787 initForm() {
4788 if (!this._font) {
4789 throw new Error('Must set a font before calling initForm method');
4790 }
4791 this._acroform = {
4792 fonts: {},
4793 defaultFont: this._font.name
4794 };
4795 this._acroform.fonts[this._font.id] = this._font.ref();
4796 let data = {
4797 Fields: [],
4798 NeedAppearances: true,
4799 DA: new String(`/${this._font.id} 0 Tf 0 g`),
4800 DR: {
4801 Font: {}
4802 }
4803 };
4804 data.DR.Font[this._font.id] = this._font.ref();
4805 const AcroForm = this.ref(data);
4806 this._root.data.AcroForm = AcroForm;
4807 return this;
4808 },
4809 /**
4810 * Called automatically by document.js
4811 */
4812 endAcroForm() {
4813 if (this._root.data.AcroForm) {
4814 if (!Object.keys(this._acroform.fonts).length && !this._acroform.defaultFont) {
4815 throw new Error('No fonts specified for PDF form');
4816 }
4817 let fontDict = this._root.data.AcroForm.data.DR.Font;
4818 Object.keys(this._acroform.fonts).forEach(name => {
4819 fontDict[name] = this._acroform.fonts[name];
4820 });
4821 this._root.data.AcroForm.data.Fields.forEach(fieldRef => {
4822 this._endChild(fieldRef);
4823 });
4824 this._root.data.AcroForm.end();
4825 }
4826 return this;
4827 },
4828 _endChild(ref) {
4829 if (Array.isArray(ref.data.Kids)) {
4830 ref.data.Kids.forEach(childRef => {
4831 this._endChild(childRef);
4832 });
4833 ref.end();
4834 }
4835 return this;
4836 },
4837 /**
4838 * Creates and adds a form field to the document. Form fields are intermediate
4839 * nodes in a PDF form that are used to specify form name heirarchy and form
4840 * value defaults.
4841 * @param {string} name - field name (T attribute in field dictionary)
4842 * @param {object} options - other attributes to include in field dictionary
4843 */
4844 formField(name, options = {}) {
4845 let fieldDict = this._fieldDict(name, null, options);
4846 let fieldRef = this.ref(fieldDict);
4847 this._addToParent(fieldRef);
4848 return fieldRef;
4849 },
4850 /**
4851 * Creates and adds a Form Annotation to the document. Form annotations are
4852 * called Widget annotations internally within a PDF file.
4853 * @param {string} name - form field name (T attribute of widget annotation
4854 * dictionary)
4855 * @param {number} x
4856 * @param {number} y
4857 * @param {number} w
4858 * @param {number} h
4859 * @param {object} options
4860 */
4861 formAnnotation(name, type, x, y, w, h, options = {}) {
4862 let fieldDict = this._fieldDict(name, type, options);
4863 fieldDict.Subtype = 'Widget';
4864 if (fieldDict.F === undefined) {
4865 fieldDict.F = 4; // print the annotation
4866 }
4867
4868 // Add Field annot to page, and get it's ref
4869 this.annotate(x, y, w, h, fieldDict);
4870 let annotRef = this.page.annotations[this.page.annotations.length - 1];
4871 return this._addToParent(annotRef);
4872 },
4873 formText(name, x, y, w, h, options = {}) {
4874 return this.formAnnotation(name, 'text', x, y, w, h, options);
4875 },
4876 formPushButton(name, x, y, w, h, options = {}) {
4877 return this.formAnnotation(name, 'pushButton', x, y, w, h, options);
4878 },
4879 formCombo(name, x, y, w, h, options = {}) {
4880 return this.formAnnotation(name, 'combo', x, y, w, h, options);
4881 },
4882 formList(name, x, y, w, h, options = {}) {
4883 return this.formAnnotation(name, 'list', x, y, w, h, options);
4884 },
4885 formRadioButton(name, x, y, w, h, options = {}) {
4886 return this.formAnnotation(name, 'radioButton', x, y, w, h, options);
4887 },
4888 formCheckbox(name, x, y, w, h, options = {}) {
4889 return this.formAnnotation(name, 'checkbox', x, y, w, h, options);
4890 },
4891 _addToParent(fieldRef) {
4892 let parent = fieldRef.data.Parent;
4893 if (parent) {
4894 if (!parent.data.Kids) {
4895 parent.data.Kids = [];
4896 }
4897 parent.data.Kids.push(fieldRef);
4898 } else {
4899 this._root.data.AcroForm.data.Fields.push(fieldRef);
4900 }
4901 return this;
4902 },
4903 _fieldDict(name, type, options = {}) {
4904 if (!this._acroform) {
4905 throw new Error('Call document.initForms() method before adding form elements to document');
4906 }
4907 let opts = Object.assign({}, options);
4908 if (type !== null) {
4909 opts = this._resolveType(type, options);
4910 }
4911 opts = this._resolveFlags(opts);
4912 opts = this._resolveJustify(opts);
4913 opts = this._resolveFont(opts);
4914 opts = this._resolveStrings(opts);
4915 opts = this._resolveColors(opts);
4916 opts = this._resolveFormat(opts);
4917 opts.T = new String(name);
4918 if (opts.parent) {
4919 opts.Parent = opts.parent;
4920 delete opts.parent;
4921 }
4922 return opts;
4923 },
4924 _resolveType(type, opts) {
4925 if (type === 'text') {
4926 opts.FT = 'Tx';
4927 } else if (type === 'pushButton') {
4928 opts.FT = 'Btn';
4929 opts.pushButton = true;
4930 } else if (type === 'radioButton') {
4931 opts.FT = 'Btn';
4932 opts.radioButton = true;
4933 } else if (type === 'checkbox') {
4934 opts.FT = 'Btn';
4935 } else if (type === 'combo') {
4936 opts.FT = 'Ch';
4937 opts.combo = true;
4938 } else if (type === 'list') {
4939 opts.FT = 'Ch';
4940 } else {
4941 throw new Error(`Invalid form annotation type '${type}'`);
4942 }
4943 return opts;
4944 },
4945 _resolveFormat(opts) {
4946 const f = opts.format;
4947 if (f && f.type) {
4948 let fnKeystroke;
4949 let fnFormat;
4950 let params = '';
4951 if (FORMAT_SPECIAL[f.type] !== undefined) {
4952 fnKeystroke = `AFSpecial_Keystroke`;
4953 fnFormat = `AFSpecial_Format`;
4954 params = FORMAT_SPECIAL[f.type];
4955 } else {
4956 let format = f.type.charAt(0).toUpperCase() + f.type.slice(1);
4957 fnKeystroke = `AF${format}_Keystroke`;
4958 fnFormat = `AF${format}_Format`;
4959 if (f.type === 'date') {
4960 fnKeystroke += 'Ex';
4961 params = String(f.param);
4962 } else if (f.type === 'time') {
4963 params = String(f.param);
4964 } else if (f.type === 'number') {
4965 let p = Object.assign({}, FORMAT_DEFAULT.number, f);
4966 params = String([String(p.nDec), p.sepComma ? '0' : '1', '"' + p.negStyle + '"', 'null', '"' + p.currency + '"', String(p.currencyPrepend)].join(','));
4967 } else if (f.type === 'percent') {
4968 let p = Object.assign({}, FORMAT_DEFAULT.percent, f);
4969 params = String([String(p.nDec), p.sepComma ? '0' : '1'].join(','));
4970 }
4971 }
4972 opts.AA = opts.AA ? opts.AA : {};
4973 opts.AA.K = {
4974 S: 'JavaScript',
4975 JS: new String(`${fnKeystroke}(${params});`)
4976 };
4977 opts.AA.F = {
4978 S: 'JavaScript',
4979 JS: new String(`${fnFormat}(${params});`)
4980 };
4981 }
4982 delete opts.format;
4983 return opts;
4984 },
4985 _resolveColors(opts) {
4986 let color = this._normalizeColor(opts.backgroundColor);
4987 if (color) {
4988 if (!opts.MK) {
4989 opts.MK = {};
4990 }
4991 opts.MK.BG = color;
4992 }
4993 color = this._normalizeColor(opts.borderColor);
4994 if (color) {
4995 if (!opts.MK) {
4996 opts.MK = {};
4997 }
4998 opts.MK.BC = color;
4999 }
5000 delete opts.backgroundColor;
5001 delete opts.borderColor;
5002 return opts;
5003 },
5004 _resolveFlags(options) {
5005 let result = 0;
5006 Object.keys(options).forEach(key => {
5007 if (FIELD_FLAGS[key]) {
5008 if (options[key]) {
5009 result |= FIELD_FLAGS[key];
5010 }
5011 delete options[key];
5012 }
5013 });
5014 if (result !== 0) {
5015 options.Ff = options.Ff ? options.Ff : 0;
5016 options.Ff |= result;
5017 }
5018 return options;
5019 },
5020 _resolveJustify(options) {
5021 let result = 0;
5022 if (options.align !== undefined) {
5023 if (typeof FIELD_JUSTIFY[options.align] === 'number') {
5024 result = FIELD_JUSTIFY[options.align];
5025 }
5026 delete options.align;
5027 }
5028 if (result !== 0) {
5029 options.Q = result; // default
5030 }
5031 return options;
5032 },
5033 _resolveFont(options) {
5034 // add current font to document-level AcroForm dict if necessary
5035 if (this._acroform.fonts[this._font.id] === null) {
5036 this._acroform.fonts[this._font.id] = this._font.ref();
5037 }
5038
5039 // add current font to field's resource dict (RD) if not the default acroform font
5040 if (this._acroform.defaultFont !== this._font.name) {
5041 options.DR = {
5042 Font: {}
5043 };
5044
5045 // Get the fontSize option. If not set use auto sizing
5046 const fontSize = options.fontSize || 0;
5047 options.DR.Font[this._font.id] = this._font.ref();
5048 options.DA = new String(`/${this._font.id} ${fontSize} Tf 0 g`);
5049 }
5050 return options;
5051 },
5052 _resolveStrings(options) {
5053 let select = [];
5054 function appendChoices(a) {
5055 if (Array.isArray(a)) {
5056 for (let idx = 0; idx < a.length; idx++) {
5057 if (typeof a[idx] === 'string') {
5058 select.push(new String(a[idx]));
5059 } else {
5060 select.push(a[idx]);
5061 }
5062 }
5063 }
5064 }
5065 appendChoices(options.Opt);
5066 if (options.select) {
5067 appendChoices(options.select);
5068 delete options.select;
5069 }
5070 if (select.length) {
5071 options.Opt = select;
5072 }
5073 Object.keys(VALUE_MAP).forEach(key => {
5074 if (options[key] !== undefined) {
5075 options[VALUE_MAP[key]] = options[key];
5076 delete options[key];
5077 }
5078 });
5079 ['V', 'DV'].forEach(key => {
5080 if (typeof options[key] === 'string') {
5081 options[key] = new String(options[key]);
5082 }
5083 });
5084 if (options.MK && options.MK.CA) {
5085 options.MK.CA = new String(options.MK.CA);
5086 }
5087 if (options.label) {
5088 options.MK = options.MK ? options.MK : {};
5089 options.MK.CA = new String(options.label);
5090 delete options.label;
5091 }
5092 return options;
5093 }
5094};
5095
5096var AttachmentsMixin = {
5097 /**
5098 * Embed contents of `src` in PDF
5099 * @param {Buffer | ArrayBuffer | string} src input Buffer, ArrayBuffer, base64 encoded string or path to file
5100 * @param {object} options
5101 * * options.name: filename to be shown in PDF, will use `src` if none set
5102 * * options.type: filetype to be shown in PDF
5103 * * options.description: description to be shown in PDF
5104 * * options.hidden: if true, do not add attachment to EmbeddedFiles dictionary. Useful for file attachment annotations
5105 * * options.creationDate: override creation date
5106 * * options.modifiedDate: override modified date
5107 * @returns filespec reference
5108 */
5109 file(src, options = {}) {
5110 options.name = options.name || src;
5111 const refBody = {
5112 Type: 'EmbeddedFile',
5113 Params: {}
5114 };
5115 let data;
5116 if (!src) {
5117 throw new Error('No src specified');
5118 }
5119 if (Buffer.isBuffer(src)) {
5120 data = src;
5121 } else if (src instanceof ArrayBuffer) {
5122 data = Buffer.from(new Uint8Array(src));
5123 } else {
5124 let match;
5125 if (match = /^data:(.*?);base64,(.*)$/.exec(src)) {
5126 if (match[1]) {
5127 refBody.Subtype = match[1].replace('/', '#2F');
5128 }
5129 data = Buffer.from(match[2], 'base64');
5130 } else {
5131 data = fs.readFileSync(src);
5132 if (!data) {
5133 throw new Error(`Could not read contents of file at filepath ${src}`);
5134 }
5135
5136 // update CreationDate and ModDate
5137 const {
5138 birthtime,
5139 ctime
5140 } = fs.statSync(src);
5141 refBody.Params.CreationDate = birthtime;
5142 refBody.Params.ModDate = ctime;
5143 }
5144 }
5145
5146 // override creation date and modified date
5147 if (options.creationDate instanceof Date) {
5148 refBody.Params.CreationDate = options.creationDate;
5149 }
5150 if (options.modifiedDate instanceof Date) {
5151 refBody.Params.ModDate = options.modifiedDate;
5152 }
5153 // add optional subtype
5154 if (options.type) {
5155 refBody.Subtype = options.type.replace('/', '#2F');
5156 }
5157
5158 // add checksum and size information
5159 const checksum = CryptoJS.MD5(CryptoJS.lib.WordArray.create(new Uint8Array(data)));
5160 refBody.Params.CheckSum = new String(checksum);
5161 refBody.Params.Size = data.byteLength;
5162
5163 // save some space when embedding the same file again
5164 // if a file with the same name and metadata exists, reuse its reference
5165 let ref;
5166 if (!this._fileRegistry) this._fileRegistry = {};
5167 let file = this._fileRegistry[options.name];
5168 if (file && isEqual(refBody, file)) {
5169 ref = file.ref;
5170 } else {
5171 ref = this.ref(refBody);
5172 ref.end(data);
5173 this._fileRegistry[options.name] = {
5174 ...refBody,
5175 ref
5176 };
5177 }
5178 // add filespec for embedded file
5179 const fileSpecBody = {
5180 Type: 'Filespec',
5181 F: new String(options.name),
5182 EF: {
5183 F: ref
5184 },
5185 UF: new String(options.name)
5186 };
5187 if (options.description) {
5188 fileSpecBody.Desc = new String(options.description);
5189 }
5190 const filespec = this.ref(fileSpecBody);
5191 filespec.end();
5192 if (!options.hidden) {
5193 this.addNamedEmbeddedFile(options.name, filespec);
5194 }
5195 return filespec;
5196 }
5197};
5198
5199/** check two embedded file metadata objects for equality */
5200function isEqual(a, b) {
5201 return a.Subtype === b.Subtype && a.Params.CheckSum.toString() === b.Params.CheckSum.toString() && a.Params.Size === b.Params.Size && a.Params.CreationDate === b.Params.CreationDate && a.Params.ModDate === b.Params.ModDate;
5202}
5203
5204var PDFA = {
5205 initPDFA(pSubset) {
5206 if (pSubset.charAt(pSubset.length - 3) === '-') {
5207 this.subset_conformance = pSubset.charAt(pSubset.length - 1).toUpperCase();
5208 this.subset = parseInt(pSubset.charAt(pSubset.length - 2));
5209 } else {
5210 // Default to Basic conformance when user doesn't specify
5211 this.subset_conformance = 'B';
5212 this.subset = parseInt(pSubset.charAt(pSubset.length - 1));
5213 }
5214 },
5215 endSubset() {
5216 this._addPdfaMetadata();
5217 this._addColorOutputIntent(`${__dirname}/data/sRGB_IEC61966_2_1.icc`);
5218 },
5219 _addColorOutputIntent(pICCPath) {
5220 const iccProfile = fs.readFileSync(pICCPath);
5221 const colorProfileRef = this.ref({
5222 Length: iccProfile.length,
5223 N: 3
5224 });
5225 colorProfileRef.write(iccProfile);
5226 colorProfileRef.end();
5227 const intentRef = this.ref({
5228 Type: 'OutputIntent',
5229 S: 'GTS_PDFA1',
5230 Info: new String('sRGB IEC61966-2.1'),
5231 OutputConditionIdentifier: new String('sRGB IEC61966-2.1'),
5232 DestOutputProfile: colorProfileRef
5233 });
5234 intentRef.end();
5235 this._root.data.OutputIntents = [intentRef];
5236 },
5237 _getPdfaid() {
5238 return `
5239 <rdf:Description xmlns:pdfaid="http://www.aiim.org/pdfa/ns/id/" rdf:about="">
5240 <pdfaid:part>${this.subset}</pdfaid:part>
5241 <pdfaid:conformance>${this.subset_conformance}</pdfaid:conformance>
5242 </rdf:Description>
5243 `;
5244 },
5245 _addPdfaMetadata() {
5246 this.appendXML(this._getPdfaid());
5247 }
5248};
5249
5250var PDFUA = {
5251 initPDFUA() {
5252 this.subset = 1;
5253 },
5254 endSubset() {
5255 this._addPdfuaMetadata();
5256 },
5257 _addPdfuaMetadata() {
5258 this.appendXML(this._getPdfuaid());
5259 },
5260 _getPdfuaid() {
5261 return `
5262 <rdf:Description xmlns:pdfuaid="http://www.aiim.org/pdfua/ns/id/" rdf:about="">
5263 <pdfuaid:part>${this.subset}</pdfuaid:part>
5264 </rdf:Description>
5265 `;
5266 }
5267};
5268
5269var SubsetMixin = {
5270 _importSubset(subset) {
5271 Object.assign(this, subset);
5272 },
5273 initSubset(options) {
5274 switch (options.subset) {
5275 case 'PDF/A-1':
5276 case 'PDF/A-1a':
5277 case 'PDF/A-1b':
5278 case 'PDF/A-2':
5279 case 'PDF/A-2a':
5280 case 'PDF/A-2b':
5281 case 'PDF/A-3':
5282 case 'PDF/A-3a':
5283 case 'PDF/A-3b':
5284 this._importSubset(PDFA);
5285 this.initPDFA(options.subset);
5286 break;
5287 case 'PDF/UA':
5288 this._importSubset(PDFUA);
5289 this.initPDFUA();
5290 break;
5291 }
5292 }
5293};
5294
5295class PDFMetadata {
5296 constructor() {
5297 this._metadata = `
5298 <?xpacket begin="\ufeff" id="W5M0MpCehiHzreSzNTczkc9d"?>
5299 <x:xmpmeta xmlns:x="adobe:ns:meta/">
5300 <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
5301 `;
5302 }
5303 _closeTags() {
5304 this._metadata = this._metadata.concat(`
5305 </rdf:RDF>
5306 </x:xmpmeta>
5307 <?xpacket end="w"?>
5308 `);
5309 }
5310 append(xml, newline = true) {
5311 this._metadata = this._metadata.concat(xml);
5312 if (newline) this._metadata = this._metadata.concat('\n');
5313 }
5314 getXML() {
5315 return this._metadata;
5316 }
5317 getLength() {
5318 return this._metadata.length;
5319 }
5320 end() {
5321 this._closeTags();
5322 this._metadata = this._metadata.trim();
5323 }
5324}
5325
5326var MetadataMixin = {
5327 initMetadata() {
5328 this.metadata = new PDFMetadata();
5329 },
5330 appendXML(xml, newline = true) {
5331 this.metadata.append(xml, newline);
5332 },
5333 _addInfo() {
5334 this.appendXML(`
5335 <rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/">
5336 <xmp:CreateDate>${this.info.CreationDate.toISOString().split('.')[0] + "Z"}</xmp:CreateDate>
5337 <xmp:CreatorTool>${this.info.Creator}</xmp:CreatorTool>
5338 </rdf:Description>
5339 `);
5340 if (this.info.Title || this.info.Author || this.info.Subject) {
5341 this.appendXML(`
5342 <rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">
5343 `);
5344 if (this.info.Title) {
5345 this.appendXML(`
5346 <dc:title>
5347 <rdf:Alt>
5348 <rdf:li xml:lang="x-default">${this.info.Title}</rdf:li>
5349 </rdf:Alt>
5350 </dc:title>
5351 `);
5352 }
5353 if (this.info.Author) {
5354 this.appendXML(`
5355 <dc:creator>
5356 <rdf:Seq>
5357 <rdf:li>${this.info.Author}</rdf:li>
5358 </rdf:Seq>
5359 </dc:creator>
5360 `);
5361 }
5362 if (this.info.Subject) {
5363 this.appendXML(`
5364 <dc:description>
5365 <rdf:Alt>
5366 <rdf:li xml:lang="x-default">${this.info.Subject}</rdf:li>
5367 </rdf:Alt>
5368 </dc:description>
5369 `);
5370 }
5371 this.appendXML(`
5372 </rdf:Description>
5373 `);
5374 }
5375 this.appendXML(`
5376 <rdf:Description rdf:about="" xmlns:pdf="http://ns.adobe.com/pdf/1.3/">
5377 <pdf:Producer>${this.info.Creator}</pdf:Producer>`, false);
5378 if (this.info.Keywords) {
5379 this.appendXML(`
5380 <pdf:Keywords>${this.info.Keywords}</pdf:Keywords>`, false);
5381 }
5382 this.appendXML(`
5383 </rdf:Description>
5384 `);
5385 },
5386 endMetadata() {
5387 this._addInfo();
5388 this.metadata.end();
5389
5390 /*
5391 Metadata was introduced in PDF 1.4, so adding it to 1.3
5392 will likely only take up more space.
5393 */
5394 if (this.version != 1.3) {
5395 this.metadataRef = this.ref({
5396 length: this.metadata.getLength(),
5397 Type: 'Metadata',
5398 Subtype: 'XML'
5399 });
5400 this.metadataRef.compress = false;
5401 this.metadataRef.write(Buffer.from(this.metadata.getXML(), 'utf-8'));
5402 this.metadataRef.end();
5403 this._root.data.Metadata = this.metadataRef;
5404 }
5405 }
5406};
5407
5408/*
5409PDFDocument - represents an entire PDF document
5410By Devon Govett
5411*/
5412class PDFDocument extends stream.Readable {
5413 constructor(options = {}) {
5414 super(options);
5415 this.options = options;
5416
5417 // PDF version
5418 switch (options.pdfVersion) {
5419 case '1.4':
5420 this.version = 1.4;
5421 break;
5422 case '1.5':
5423 this.version = 1.5;
5424 break;
5425 case '1.6':
5426 this.version = 1.6;
5427 break;
5428 case '1.7':
5429 case '1.7ext3':
5430 this.version = 1.7;
5431 break;
5432 default:
5433 this.version = 1.3;
5434 break;
5435 }
5436
5437 // Whether streams should be compressed
5438 this.compress = this.options.compress != null ? this.options.compress : true;
5439 this._pageBuffer = [];
5440 this._pageBufferStart = 0;
5441
5442 // The PDF object store
5443 this._offsets = [];
5444 this._waiting = 0;
5445 this._ended = false;
5446 this._offset = 0;
5447 const Pages = this.ref({
5448 Type: 'Pages',
5449 Count: 0,
5450 Kids: []
5451 });
5452 const Names = this.ref({
5453 Dests: new PDFNameTree()
5454 });
5455 this._root = this.ref({
5456 Type: 'Catalog',
5457 Pages,
5458 Names
5459 });
5460 if (this.options.lang) {
5461 this._root.data.Lang = new String(this.options.lang);
5462 }
5463
5464 // The current page
5465 this.page = null;
5466
5467 // Initialize mixins
5468 this.initMetadata();
5469 this.initColor();
5470 this.initVector();
5471 this.initFonts(options.font);
5472 this.initText();
5473 this.initImages();
5474 this.initOutline();
5475 this.initMarkings(options);
5476 this.initSubset(options);
5477
5478 // Initialize the metadata
5479 this.info = {
5480 Producer: 'PDFKit',
5481 Creator: 'PDFKit',
5482 CreationDate: new Date()
5483 };
5484 if (this.options.info) {
5485 for (let key in this.options.info) {
5486 const val = this.options.info[key];
5487 this.info[key] = val;
5488 }
5489 }
5490 if (this.options.displayTitle) {
5491 this._root.data.ViewerPreferences = this.ref({
5492 DisplayDocTitle: true
5493 });
5494 }
5495
5496 // Generate file ID
5497 this._id = PDFSecurity.generateFileID(this.info);
5498
5499 // Initialize security settings
5500 this._security = PDFSecurity.create(this, options);
5501
5502 // Write the header
5503 // PDF version
5504 this._write(`%PDF-${this.version}`);
5505
5506 // 4 binary chars, as recommended by the spec
5507 this._write('%\xFF\xFF\xFF\xFF');
5508
5509 // Add the first page
5510 if (this.options.autoFirstPage !== false) {
5511 this.addPage();
5512 }
5513 }
5514 addPage(options) {
5515 if (options == null) {
5516 ({
5517 options
5518 } = this);
5519 }
5520
5521 // end the current page if needed
5522 if (!this.options.bufferPages) {
5523 this.flushPages();
5524 }
5525
5526 // create a page object
5527 this.page = new PDFPage(this, options);
5528 this._pageBuffer.push(this.page);
5529
5530 // add the page to the object store
5531 const pages = this._root.data.Pages.data;
5532 pages.Kids.push(this.page.dictionary);
5533 pages.Count++;
5534
5535 // reset x and y coordinates
5536 this.x = this.page.margins.left;
5537 this.y = this.page.margins.top;
5538
5539 // flip PDF coordinate system so that the origin is in
5540 // the top left rather than the bottom left
5541 this._ctm = [1, 0, 0, 1, 0, 0];
5542 this.transform(1, 0, 0, -1, 0, this.page.height);
5543 this.emit('pageAdded');
5544 return this;
5545 }
5546 continueOnNewPage(options) {
5547 const pageMarkings = this.endPageMarkings(this.page);
5548 this.addPage(options);
5549 this.initPageMarkings(pageMarkings);
5550 return this;
5551 }
5552 bufferedPageRange() {
5553 return {
5554 start: this._pageBufferStart,
5555 count: this._pageBuffer.length
5556 };
5557 }
5558 switchToPage(n) {
5559 let page;
5560 if (!(page = this._pageBuffer[n - this._pageBufferStart])) {
5561 throw new Error(`switchToPage(${n}) out of bounds, current buffer covers pages ${this._pageBufferStart} to ${this._pageBufferStart + this._pageBuffer.length - 1}`);
5562 }
5563 return this.page = page;
5564 }
5565 flushPages() {
5566 // this local variable exists so we're future-proof against
5567 // reentrant calls to flushPages.
5568 const pages = this._pageBuffer;
5569 this._pageBuffer = [];
5570 this._pageBufferStart += pages.length;
5571 for (let page of pages) {
5572 this.endPageMarkings(page);
5573 page.end();
5574 }
5575 }
5576 addNamedDestination(name, ...args) {
5577 if (args.length === 0) {
5578 args = ['XYZ', null, null, null];
5579 }
5580 if (args[0] === 'XYZ' && args[2] !== null) {
5581 args[2] = this.page.height - args[2];
5582 }
5583 args.unshift(this.page.dictionary);
5584 this._root.data.Names.data.Dests.add(name, args);
5585 }
5586 addNamedEmbeddedFile(name, ref) {
5587 if (!this._root.data.Names.data.EmbeddedFiles) {
5588 // disabling /Limits for this tree fixes attachments not showing in Adobe Reader
5589 this._root.data.Names.data.EmbeddedFiles = new PDFNameTree({
5590 limits: false
5591 });
5592 }
5593
5594 // add filespec to EmbeddedFiles
5595 this._root.data.Names.data.EmbeddedFiles.add(name, ref);
5596 }
5597 addNamedJavaScript(name, js) {
5598 if (!this._root.data.Names.data.JavaScript) {
5599 this._root.data.Names.data.JavaScript = new PDFNameTree();
5600 }
5601 let data = {
5602 JS: new String(js),
5603 S: 'JavaScript'
5604 };
5605 this._root.data.Names.data.JavaScript.add(name, data);
5606 }
5607 ref(data) {
5608 const ref = new PDFReference(this, this._offsets.length + 1, data);
5609 this._offsets.push(null); // placeholder for this object's offset once it is finalized
5610 this._waiting++;
5611 return ref;
5612 }
5613 _read() {}
5614 // do nothing, but this method is required by node
5615
5616 _write(data) {
5617 if (!Buffer.isBuffer(data)) {
5618 data = Buffer.from(data + '\n', 'binary');
5619 }
5620 this.push(data);
5621 return this._offset += data.length;
5622 }
5623 addContent(data) {
5624 this.page.write(data);
5625 return this;
5626 }
5627 _refEnd(ref) {
5628 this._offsets[ref.id - 1] = ref.offset;
5629 if (--this._waiting === 0 && this._ended) {
5630 this._finalize();
5631 return this._ended = false;
5632 }
5633 }
5634 end() {
5635 this.flushPages();
5636 this._info = this.ref();
5637 for (let key in this.info) {
5638 let val = this.info[key];
5639 if (typeof val === 'string') {
5640 val = new String(val);
5641 }
5642 let entry = this.ref(val);
5643 entry.end();
5644 this._info.data[key] = entry;
5645 }
5646 this._info.end();
5647 for (let name in this._fontFamilies) {
5648 const font = this._fontFamilies[name];
5649 font.finalize();
5650 }
5651 this.endOutline();
5652 this.endMarkings();
5653 if (this.subset) {
5654 this.endSubset();
5655 }
5656 this.endMetadata();
5657 this._root.end();
5658 this._root.data.Pages.end();
5659 this._root.data.Names.end();
5660 this.endAcroForm();
5661 if (this._root.data.ViewerPreferences) {
5662 this._root.data.ViewerPreferences.end();
5663 }
5664 if (this._security) {
5665 this._security.end();
5666 }
5667 if (this._waiting === 0) {
5668 return this._finalize();
5669 } else {
5670 return this._ended = true;
5671 }
5672 }
5673 _finalize() {
5674 // generate xref
5675 const xRefOffset = this._offset;
5676 this._write('xref');
5677 this._write(`0 ${this._offsets.length + 1}`);
5678 this._write('0000000000 65535 f ');
5679 for (let offset of this._offsets) {
5680 offset = `0000000000${offset}`.slice(-10);
5681 this._write(offset + ' 00000 n ');
5682 }
5683
5684 // trailer
5685 const trailer = {
5686 Size: this._offsets.length + 1,
5687 Root: this._root,
5688 Info: this._info,
5689 ID: [this._id, this._id]
5690 };
5691 if (this._security) {
5692 trailer.Encrypt = this._security.dictionary;
5693 }
5694 this._write('trailer');
5695 this._write(PDFObject.convert(trailer));
5696 this._write('startxref');
5697 this._write(`${xRefOffset}`);
5698 this._write('%%EOF');
5699
5700 // end the stream
5701 return this.push(null);
5702 }
5703 toString() {
5704 return '[object PDFDocument]';
5705 }
5706}
5707const mixin = methods => {
5708 Object.assign(PDFDocument.prototype, methods);
5709};
5710mixin(MetadataMixin);
5711mixin(ColorMixin);
5712mixin(VectorMixin);
5713mixin(FontsMixin);
5714mixin(TextMixin);
5715mixin(ImagesMixin);
5716mixin(AnnotationsMixin);
5717mixin(OutlineMixin);
5718mixin(MarkingsMixin);
5719mixin(AcroFormMixin);
5720mixin(AttachmentsMixin);
5721mixin(SubsetMixin);
5722PDFDocument.LineWrapper = LineWrapper;
5723
5724module.exports = PDFDocument;
5725//# sourceMappingURL=pdfkit.js.map