pdfkit 0.9.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/js/pdfkit.js CHANGED
@@ -2,24 +2,64 @@
2
2
 
3
3
  function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
4
4
 
5
+ var stream = _interopDefault(require('stream'));
6
+ var fs = require('fs');
5
7
  var zlib = _interopDefault(require('zlib'));
6
8
  var CryptoJS = _interopDefault(require('crypto-js'));
7
- var saslprep = _interopDefault(require('saslprep'));
8
- var fs = require('fs');
9
9
  var fontkit = _interopDefault(require('fontkit'));
10
10
  var events = require('events');
11
11
  var LineBreaker = _interopDefault(require('linebreak'));
12
12
  var PNG = _interopDefault(require('png-js'));
13
- var stream = _interopDefault(require('stream'));
14
13
 
15
14
  /*
16
15
  PDFAbstractReference - abstract class for PDF reference
17
16
  */
18
-
19
17
  class PDFAbstractReference {
20
18
  toString() {
21
19
  throw new Error('Must be implemented by subclasses');
22
20
  }
21
+
22
+ }
23
+
24
+ /*
25
+ PDFNameTree - represents a name tree object
26
+ */
27
+
28
+ class PDFNameTree {
29
+ constructor() {
30
+ this._items = {};
31
+ }
32
+
33
+ add(key, val) {
34
+ return this._items[key] = val;
35
+ }
36
+
37
+ get(key) {
38
+ return this._items[key];
39
+ }
40
+
41
+ toString() {
42
+ // Needs to be sorted by key
43
+ const sortedKeys = Object.keys(this._items).sort((a, b) => a.localeCompare(b));
44
+ const out = ['<<'];
45
+
46
+ if (sortedKeys.length > 1) {
47
+ const first = sortedKeys[0],
48
+ last = sortedKeys[sortedKeys.length - 1];
49
+ out.push(` /Limits ${PDFObject.convert([new String(first), new String(last)])}`);
50
+ }
51
+
52
+ out.push(' /Names [');
53
+
54
+ for (let key of sortedKeys) {
55
+ out.push(` ${PDFObject.convert(new String(key))} ${PDFObject.convert(this._items[key])}`);
56
+ }
57
+
58
+ out.push(']');
59
+ out.push('>>');
60
+ return out.join('\n');
61
+ }
62
+
23
63
  }
24
64
 
25
65
  /*
@@ -39,11 +79,11 @@ const escapable = {
39
79
  '\\': '\\\\',
40
80
  '(': '\\(',
41
81
  ')': '\\)'
42
- };
82
+ }; // Convert little endian UTF-16 to big endian
43
83
 
44
- // Convert little endian UTF-16 to big endian
45
84
  const swapBytes = function (buff) {
46
85
  const l = buff.length;
86
+
47
87
  if (l & 0x01) {
48
88
  throw new Error('Buffer length must be even');
49
89
  } else {
@@ -61,53 +101,48 @@ class PDFObject {
61
101
  static convert(object, encryptFn = null) {
62
102
  // String literals are converted to the PDF name type
63
103
  if (typeof object === 'string') {
64
- return `/${object}`;
65
-
66
- // String objects are converted to PDF strings (UTF-16)
104
+ return `/${object}`; // String objects are converted to PDF strings (UTF-16)
67
105
  } else if (object instanceof String) {
68
- let string = object;
69
- // Detect if this is a unicode string
106
+ let string = object; // Detect if this is a unicode string
107
+
70
108
  let isUnicode = false;
109
+
71
110
  for (let i = 0, end = string.length; i < end; i++) {
72
111
  if (string.charCodeAt(i) > 0x7f) {
73
112
  isUnicode = true;
74
113
  break;
75
114
  }
76
- }
115
+ } // If so, encode it as big endian UTF-16
116
+
77
117
 
78
- // If so, encode it as big endian UTF-16
79
118
  let stringBuffer;
119
+
80
120
  if (isUnicode) {
81
121
  stringBuffer = swapBytes(Buffer.from(`\ufeff${string}`, 'utf16le'));
82
122
  } else {
83
123
  stringBuffer = Buffer.from(string.valueOf(), 'ascii');
84
- }
124
+ } // Encrypt the string when necessary
125
+
85
126
 
86
- // Encrypt the string when necessary
87
127
  if (encryptFn) {
88
128
  string = encryptFn(stringBuffer).toString('binary');
89
129
  } else {
90
130
  string = stringBuffer.toString('binary');
91
- }
131
+ } // Escape characters as required by the spec
92
132
 
93
- // Escape characters as required by the spec
94
- string = string.replace(escapableRe, c => escapable[c]);
95
133
 
96
- return `(${string})`;
97
-
98
- // Buffers are converted to PDF hex strings
134
+ string = string.replace(escapableRe, c => escapable[c]);
135
+ return `(${string})`; // Buffers are converted to PDF hex strings
99
136
  } else if (Buffer.isBuffer(object)) {
100
137
  return `<${object.toString('hex')}>`;
101
- } else if (object instanceof PDFAbstractReference) {
138
+ } else if (object instanceof PDFAbstractReference || object instanceof PDFNameTree) {
102
139
  return object.toString();
103
140
  } else if (object instanceof Date) {
104
- 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';
141
+ 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'; // Encrypt the string when necessary
105
142
 
106
- // Encrypt the string when necessary
107
143
  if (encryptFn) {
108
- string = encryptFn(new Buffer(string, 'ascii')).toString('binary');
144
+ string = encryptFn(new Buffer(string, 'ascii')).toString('binary'); // Escape characters as required by the spec
109
145
 
110
- // Escape characters as required by the spec
111
146
  string = string.replace(escapableRe, c => escapable[c]);
112
147
  }
113
148
 
@@ -117,6 +152,7 @@ class PDFObject {
117
152
  return `[${items}]`;
118
153
  } else if ({}.toString.call(object) === '[object Object]') {
119
154
  const out = ['<<'];
155
+
120
156
  for (let key in object) {
121
157
  const val = object[key];
122
158
  out.push(`/${key} ${PDFObject.convert(val, encryptFn)}`);
@@ -138,6 +174,7 @@ class PDFObject {
138
174
 
139
175
  throw new Error(`unsupported number: ${n}`);
140
176
  }
177
+
141
178
  }
142
179
 
143
180
  /*
@@ -146,13 +183,10 @@ By Devon Govett
146
183
  */
147
184
 
148
185
  class PDFReference extends PDFAbstractReference {
149
- constructor(document, id, data) {
186
+ constructor(document, id, data = {}) {
150
187
  super();
151
188
  this.document = document;
152
189
  this.id = id;
153
- if (data == null) {
154
- data = {};
155
- }
156
190
  this.data = data;
157
191
  this.gen = 0;
158
192
  this.compress = this.document.compress && !this.data.Filter;
@@ -166,11 +200,14 @@ class PDFReference extends PDFAbstractReference {
166
200
  }
167
201
 
168
202
  this.uncompressedLength += chunk.length;
203
+
169
204
  if (this.data.Length == null) {
170
205
  this.data.Length = 0;
171
206
  }
207
+
172
208
  this.buffer.push(chunk);
173
209
  this.data.Length += chunk.length;
210
+
174
211
  if (this.compress) {
175
212
  return this.data.Filter = 'FlateDecode';
176
213
  }
@@ -180,16 +217,17 @@ class PDFReference extends PDFAbstractReference {
180
217
  if (chunk) {
181
218
  this.write(chunk);
182
219
  }
220
+
183
221
  return this.finalize();
184
222
  }
185
223
 
186
224
  finalize() {
187
225
  this.offset = this.document._offset;
188
-
189
226
  const encryptFn = this.document._security ? this.document._security.getEncryptFn(this.id, this.gen) : null;
190
227
 
191
228
  if (this.buffer.length) {
192
229
  this.buffer = Buffer.concat(this.buffer);
230
+
193
231
  if (this.compress) {
194
232
  this.buffer = zlib.deflateSync(this.buffer);
195
233
  }
@@ -202,36 +240,40 @@ class PDFReference extends PDFAbstractReference {
202
240
  }
203
241
 
204
242
  this.document._write(`${this.id} ${this.gen} obj`);
243
+
205
244
  this.document._write(PDFObject.convert(this.data, encryptFn));
206
245
 
207
246
  if (this.buffer.length) {
208
247
  this.document._write('stream');
248
+
209
249
  this.document._write(this.buffer);
210
250
 
211
251
  this.buffer = []; // free up memory
252
+
212
253
  this.document._write('\nendstream');
213
254
  }
214
255
 
215
256
  this.document._write('endobj');
257
+
216
258
  this.document._refEnd(this);
217
259
  }
260
+
218
261
  toString() {
219
262
  return `${this.id} ${this.gen} R`;
220
263
  }
264
+
221
265
  }
222
266
 
223
267
  /*
224
268
  PDFPage - represents a single page in the PDF document
225
269
  By Devon Govett
226
270
  */
227
-
228
271
  const DEFAULT_MARGINS = {
229
272
  top: 72,
230
273
  left: 72,
231
274
  bottom: 72,
232
275
  right: 72
233
276
  };
234
-
235
277
  const SIZES = {
236
278
  '4A0': [4767.87, 6740.79],
237
279
  '2A0': [3370.39, 4767.87],
@@ -286,41 +328,32 @@ const SIZES = {
286
328
  };
287
329
 
288
330
  class PDFPage {
289
- constructor(document, options) {
331
+ constructor(document, options = {}) {
290
332
  this.document = document;
291
- if (options == null) {
292
- options = {};
293
- }
294
333
  this.size = options.size || 'letter';
295
- this.layout = options.layout || 'portrait';
334
+ this.layout = options.layout || 'portrait'; // process margins
296
335
 
297
- // process margins
298
336
  if (typeof options.margin === 'number') {
299
337
  this.margins = {
300
338
  top: options.margin,
301
339
  left: options.margin,
302
340
  bottom: options.margin,
303
341
  right: options.margin
304
- };
305
-
306
- // default to 1 inch margins
342
+ }; // default to 1 inch margins
307
343
  } else {
308
344
  this.margins = options.margins || DEFAULT_MARGINS;
309
- }
345
+ } // calculate page dimensions
346
+
310
347
 
311
- // calculate page dimensions
312
348
  const dimensions = Array.isArray(this.size) ? this.size : SIZES[this.size.toUpperCase()];
313
349
  this.width = dimensions[this.layout === 'portrait' ? 0 : 1];
314
350
  this.height = dimensions[this.layout === 'portrait' ? 1 : 0];
351
+ this.content = this.document.ref(); // Initialize the Font, XObject, and ExtGState dictionaries
315
352
 
316
- this.content = this.document.ref();
317
-
318
- // Initialize the Font, XObject, and ExtGState dictionaries
319
353
  this.resources = this.document.ref({
320
354
  ProcSet: ['PDF', 'Text', 'ImageB', 'ImageC', 'ImageI']
321
- });
355
+ }); // The page dictionary
322
356
 
323
- // The page dictionary
324
357
  this.dictionary = this.document.ref({
325
358
  Type: 'Page',
326
359
  Parent: this.document._root.data.Pages,
@@ -328,9 +361,9 @@ class PDFPage {
328
361
  Contents: this.content,
329
362
  Resources: this.resources
330
363
  });
331
- }
364
+ } // Lazily create these dictionaries
365
+
332
366
 
333
- // Lazily create these dictionaries
334
367
  get fonts() {
335
368
  const data = this.resources.data;
336
369
  return data.Font != null ? data.Font : data.Font = {};
@@ -369,6 +402,433 @@ class PDFPage {
369
402
  this.resources.end();
370
403
  return this.content.end();
371
404
  }
405
+
406
+ }
407
+
408
+ /**
409
+ * Check if value is in a range group.
410
+ * @param {number} value
411
+ * @param {number[]} rangeGroup
412
+ * @returns {boolean}
413
+ */
414
+ function inRange(value, rangeGroup) {
415
+ if (value < rangeGroup[0]) return false;
416
+ let startRange = 0;
417
+ let endRange = rangeGroup.length / 2;
418
+
419
+ while (startRange <= endRange) {
420
+ const middleRange = Math.floor((startRange + endRange) / 2); // actual array index
421
+
422
+ const arrayIndex = middleRange * 2; // Check if value is in range pointed by actual index
423
+
424
+ if (value >= rangeGroup[arrayIndex] && value <= rangeGroup[arrayIndex + 1]) {
425
+ return true;
426
+ }
427
+
428
+ if (value > rangeGroup[arrayIndex + 1]) {
429
+ // Search Right Side Of Array
430
+ startRange = middleRange + 1;
431
+ } else {
432
+ // Search Left Side Of Array
433
+ endRange = middleRange - 1;
434
+ }
435
+ }
436
+
437
+ return false;
438
+ }
439
+
440
+ /* eslint-disable prettier/prettier */
441
+
442
+ /**
443
+ * A.1 Unassigned code points in Unicode 3.2
444
+ * @link https://tools.ietf.org/html/rfc3454#appendix-A.1
445
+ */
446
+
447
+ const 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];
448
+ /* eslint-enable */
449
+
450
+ const isUnassignedCodePoint = character => inRange(character, unassigned_code_points);
451
+ /* eslint-disable prettier/prettier */
452
+
453
+ /**
454
+ * B.1 Commonly mapped to nothing
455
+ * @link https://tools.ietf.org/html/rfc3454#appendix-B.1
456
+ */
457
+
458
+
459
+ const 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];
460
+ /* eslint-enable */
461
+
462
+ const isCommonlyMappedToNothing = character => inRange(character, commonly_mapped_to_nothing);
463
+ /* eslint-disable prettier/prettier */
464
+
465
+ /**
466
+ * C.1.2 Non-ASCII space characters
467
+ * @link https://tools.ietf.org/html/rfc3454#appendix-C.1.2
468
+ */
469
+
470
+
471
+ const non_ASCII_space_characters = [0x00a0, 0x00a0
472
+ /* NO-BREAK SPACE */
473
+ , 0x1680, 0x1680
474
+ /* OGHAM SPACE MARK */
475
+ , 0x2000, 0x2000
476
+ /* EN QUAD */
477
+ , 0x2001, 0x2001
478
+ /* EM QUAD */
479
+ , 0x2002, 0x2002
480
+ /* EN SPACE */
481
+ , 0x2003, 0x2003
482
+ /* EM SPACE */
483
+ , 0x2004, 0x2004
484
+ /* THREE-PER-EM SPACE */
485
+ , 0x2005, 0x2005
486
+ /* FOUR-PER-EM SPACE */
487
+ , 0x2006, 0x2006
488
+ /* SIX-PER-EM SPACE */
489
+ , 0x2007, 0x2007
490
+ /* FIGURE SPACE */
491
+ , 0x2008, 0x2008
492
+ /* PUNCTUATION SPACE */
493
+ , 0x2009, 0x2009
494
+ /* THIN SPACE */
495
+ , 0x200a, 0x200a
496
+ /* HAIR SPACE */
497
+ , 0x200b, 0x200b
498
+ /* ZERO WIDTH SPACE */
499
+ , 0x202f, 0x202f
500
+ /* NARROW NO-BREAK SPACE */
501
+ , 0x205f, 0x205f
502
+ /* MEDIUM MATHEMATICAL SPACE */
503
+ , 0x3000, 0x3000
504
+ /* IDEOGRAPHIC SPACE */
505
+ ];
506
+ /* eslint-enable */
507
+
508
+ const isNonASCIISpaceCharacter = character => inRange(character, non_ASCII_space_characters);
509
+ /* eslint-disable prettier/prettier */
510
+
511
+
512
+ const non_ASCII_controls_characters = [
513
+ /**
514
+ * C.2.2 Non-ASCII control characters
515
+ * @link https://tools.ietf.org/html/rfc3454#appendix-C.2.2
516
+ */
517
+ 0x0080, 0x009f
518
+ /* [CONTROL CHARACTERS] */
519
+ , 0x06dd, 0x06dd
520
+ /* ARABIC END OF AYAH */
521
+ , 0x070f, 0x070f
522
+ /* SYRIAC ABBREVIATION MARK */
523
+ , 0x180e, 0x180e
524
+ /* MONGOLIAN VOWEL SEPARATOR */
525
+ , 0x200c, 0x200c
526
+ /* ZERO WIDTH NON-JOINER */
527
+ , 0x200d, 0x200d
528
+ /* ZERO WIDTH JOINER */
529
+ , 0x2028, 0x2028
530
+ /* LINE SEPARATOR */
531
+ , 0x2029, 0x2029
532
+ /* PARAGRAPH SEPARATOR */
533
+ , 0x2060, 0x2060
534
+ /* WORD JOINER */
535
+ , 0x2061, 0x2061
536
+ /* FUNCTION APPLICATION */
537
+ , 0x2062, 0x2062
538
+ /* INVISIBLE TIMES */
539
+ , 0x2063, 0x2063
540
+ /* INVISIBLE SEPARATOR */
541
+ , 0x206a, 0x206f
542
+ /* [CONTROL CHARACTERS] */
543
+ , 0xfeff, 0xfeff
544
+ /* ZERO WIDTH NO-BREAK SPACE */
545
+ , 0xfff9, 0xfffc
546
+ /* [CONTROL CHARACTERS] */
547
+ , 0x1d173, 0x1d17a
548
+ /* [MUSICAL CONTROL CHARACTERS] */
549
+ ];
550
+ const non_character_codepoints = [
551
+ /**
552
+ * C.4 Non-character code points
553
+ * @link https://tools.ietf.org/html/rfc3454#appendix-C.4
554
+ */
555
+ 0xfdd0, 0xfdef
556
+ /* [NONCHARACTER CODE POINTS] */
557
+ , 0xfffe, 0xffff
558
+ /* [NONCHARACTER CODE POINTS] */
559
+ , 0x1fffe, 0x1ffff
560
+ /* [NONCHARACTER CODE POINTS] */
561
+ , 0x2fffe, 0x2ffff
562
+ /* [NONCHARACTER CODE POINTS] */
563
+ , 0x3fffe, 0x3ffff
564
+ /* [NONCHARACTER CODE POINTS] */
565
+ , 0x4fffe, 0x4ffff
566
+ /* [NONCHARACTER CODE POINTS] */
567
+ , 0x5fffe, 0x5ffff
568
+ /* [NONCHARACTER CODE POINTS] */
569
+ , 0x6fffe, 0x6ffff
570
+ /* [NONCHARACTER CODE POINTS] */
571
+ , 0x7fffe, 0x7ffff
572
+ /* [NONCHARACTER CODE POINTS] */
573
+ , 0x8fffe, 0x8ffff
574
+ /* [NONCHARACTER CODE POINTS] */
575
+ , 0x9fffe, 0x9ffff
576
+ /* [NONCHARACTER CODE POINTS] */
577
+ , 0xafffe, 0xaffff
578
+ /* [NONCHARACTER CODE POINTS] */
579
+ , 0xbfffe, 0xbffff
580
+ /* [NONCHARACTER CODE POINTS] */
581
+ , 0xcfffe, 0xcffff
582
+ /* [NONCHARACTER CODE POINTS] */
583
+ , 0xdfffe, 0xdffff
584
+ /* [NONCHARACTER CODE POINTS] */
585
+ , 0xefffe, 0xeffff
586
+ /* [NONCHARACTER CODE POINTS] */
587
+ , 0x10fffe, 0x10ffff
588
+ /* [NONCHARACTER CODE POINTS] */
589
+ ];
590
+ /**
591
+ * 2.3. Prohibited Output
592
+ */
593
+
594
+ const prohibited_characters = [
595
+ /**
596
+ * C.2.1 ASCII control characters
597
+ * @link https://tools.ietf.org/html/rfc3454#appendix-C.2.1
598
+ */
599
+ 0, 0x001f
600
+ /* [CONTROL CHARACTERS] */
601
+ , 0x007f, 0x007f
602
+ /* DELETE */
603
+ ,
604
+ /**
605
+ * C.8 Change display properties or are deprecated
606
+ * @link https://tools.ietf.org/html/rfc3454#appendix-C.8
607
+ */
608
+ 0x0340, 0x0340
609
+ /* COMBINING GRAVE TONE MARK */
610
+ , 0x0341, 0x0341
611
+ /* COMBINING ACUTE TONE MARK */
612
+ , 0x200e, 0x200e
613
+ /* LEFT-TO-RIGHT MARK */
614
+ , 0x200f, 0x200f
615
+ /* RIGHT-TO-LEFT MARK */
616
+ , 0x202a, 0x202a
617
+ /* LEFT-TO-RIGHT EMBEDDING */
618
+ , 0x202b, 0x202b
619
+ /* RIGHT-TO-LEFT EMBEDDING */
620
+ , 0x202c, 0x202c
621
+ /* POP DIRECTIONAL FORMATTING */
622
+ , 0x202d, 0x202d
623
+ /* LEFT-TO-RIGHT OVERRIDE */
624
+ , 0x202e, 0x202e
625
+ /* RIGHT-TO-LEFT OVERRIDE */
626
+ , 0x206a, 0x206a
627
+ /* INHIBIT SYMMETRIC SWAPPING */
628
+ , 0x206b, 0x206b
629
+ /* ACTIVATE SYMMETRIC SWAPPING */
630
+ , 0x206c, 0x206c
631
+ /* INHIBIT ARABIC FORM SHAPING */
632
+ , 0x206d, 0x206d
633
+ /* ACTIVATE ARABIC FORM SHAPING */
634
+ , 0x206e, 0x206e
635
+ /* NATIONAL DIGIT SHAPES */
636
+ , 0x206f, 0x206f
637
+ /* NOMINAL DIGIT SHAPES */
638
+ ,
639
+ /**
640
+ * C.7 Inappropriate for canonical representation
641
+ * @link https://tools.ietf.org/html/rfc3454#appendix-C.7
642
+ */
643
+ 0x2ff0, 0x2ffb
644
+ /* [IDEOGRAPHIC DESCRIPTION CHARACTERS] */
645
+ ,
646
+ /**
647
+ * C.5 Surrogate codes
648
+ * @link https://tools.ietf.org/html/rfc3454#appendix-C.5
649
+ */
650
+ 0xd800, 0xdfff,
651
+ /**
652
+ * C.3 Private use
653
+ * @link https://tools.ietf.org/html/rfc3454#appendix-C.3
654
+ */
655
+ 0xe000, 0xf8ff
656
+ /* [PRIVATE USE, PLANE 0] */
657
+ ,
658
+ /**
659
+ * C.6 Inappropriate for plain text
660
+ * @link https://tools.ietf.org/html/rfc3454#appendix-C.6
661
+ */
662
+ 0xfff9, 0xfff9
663
+ /* INTERLINEAR ANNOTATION ANCHOR */
664
+ , 0xfffa, 0xfffa
665
+ /* INTERLINEAR ANNOTATION SEPARATOR */
666
+ , 0xfffb, 0xfffb
667
+ /* INTERLINEAR ANNOTATION TERMINATOR */
668
+ , 0xfffc, 0xfffc
669
+ /* OBJECT REPLACEMENT CHARACTER */
670
+ , 0xfffd, 0xfffd
671
+ /* REPLACEMENT CHARACTER */
672
+ ,
673
+ /**
674
+ * C.9 Tagging characters
675
+ * @link https://tools.ietf.org/html/rfc3454#appendix-C.9
676
+ */
677
+ 0xe0001, 0xe0001
678
+ /* LANGUAGE TAG */
679
+ , 0xe0020, 0xe007f
680
+ /* [TAGGING CHARACTERS] */
681
+ ,
682
+ /**
683
+ * C.3 Private use
684
+ * @link https://tools.ietf.org/html/rfc3454#appendix-C.3
685
+ */
686
+ 0xf0000, 0xffffd
687
+ /* [PRIVATE USE, PLANE 15] */
688
+ , 0x100000, 0x10fffd
689
+ /* [PRIVATE USE, PLANE 16] */
690
+ ];
691
+ /* eslint-enable */
692
+
693
+ const isProhibitedCharacter = character => inRange(character, non_ASCII_space_characters) || inRange(character, prohibited_characters) || inRange(character, non_ASCII_controls_characters) || inRange(character, non_character_codepoints);
694
+ /* eslint-disable prettier/prettier */
695
+
696
+ /**
697
+ * D.1 Characters with bidirectional property "R" or "AL"
698
+ * @link https://tools.ietf.org/html/rfc3454#appendix-D.1
699
+ */
700
+
701
+
702
+ const 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];
703
+ /* eslint-enable */
704
+
705
+ const isBidirectionalRAL = character => inRange(character, bidirectional_r_al);
706
+ /* eslint-disable prettier/prettier */
707
+
708
+ /**
709
+ * D.2 Characters with bidirectional property "L"
710
+ * @link https://tools.ietf.org/html/rfc3454#appendix-D.2
711
+ */
712
+
713
+
714
+ const 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];
715
+ /* eslint-enable */
716
+
717
+ const isBidirectionalL = character => inRange(character, bidirectional_l);
718
+
719
+ /**
720
+ * non-ASCII space characters [StringPrep, C.1.2] that can be
721
+ * mapped to SPACE (U+0020)
722
+ */
723
+
724
+ const mapping2space = isNonASCIISpaceCharacter;
725
+ /**
726
+ * the "commonly mapped to nothing" characters [StringPrep, B.1]
727
+ * that can be mapped to nothing.
728
+ */
729
+
730
+ const mapping2nothing = isCommonlyMappedToNothing; // utils
731
+
732
+ const getCodePoint = character => character.codePointAt(0);
733
+
734
+ const first = x => x[0];
735
+
736
+ const last = x => x[x.length - 1];
737
+ /**
738
+ * Convert provided string into an array of Unicode Code Points.
739
+ * Based on https://stackoverflow.com/a/21409165/1556249
740
+ * and https://www.npmjs.com/package/code-point-at.
741
+ * @param {string} input
742
+ * @returns {number[]}
743
+ */
744
+
745
+
746
+ function toCodePoints(input) {
747
+ const codepoints = [];
748
+ const size = input.length;
749
+
750
+ for (let i = 0; i < size; i += 1) {
751
+ const before = input.charCodeAt(i);
752
+
753
+ if (before >= 0xd800 && before <= 0xdbff && size > i + 1) {
754
+ const next = input.charCodeAt(i + 1);
755
+
756
+ if (next >= 0xdc00 && next <= 0xdfff) {
757
+ codepoints.push((before - 0xd800) * 0x400 + next - 0xdc00 + 0x10000);
758
+ i += 1;
759
+ continue;
760
+ }
761
+ }
762
+
763
+ codepoints.push(before);
764
+ }
765
+
766
+ return codepoints;
767
+ }
768
+ /**
769
+ * SASLprep.
770
+ * @param {string} input
771
+ * @param {Object} opts
772
+ * @param {boolean} opts.allowUnassigned
773
+ * @returns {string}
774
+ */
775
+
776
+
777
+ function saslprep(input, opts = {}) {
778
+ if (typeof input !== 'string') {
779
+ throw new TypeError('Expected string.');
780
+ }
781
+
782
+ if (input.length === 0) {
783
+ return '';
784
+ } // 1. Map
785
+
786
+
787
+ const mapped_input = toCodePoints(input) // 1.1 mapping to space
788
+ .map(character => mapping2space(character) ? 0x20 : character) // 1.2 mapping to nothing
789
+ .filter(character => !mapping2nothing(character)); // 2. Normalize
790
+
791
+ const normalized_input = String.fromCodePoint.apply(null, mapped_input).normalize('NFKC');
792
+ const normalized_map = toCodePoints(normalized_input); // 3. Prohibit
793
+
794
+ const hasProhibited = normalized_map.some(isProhibitedCharacter);
795
+
796
+ if (hasProhibited) {
797
+ throw new Error('Prohibited character, see https://tools.ietf.org/html/rfc4013#section-2.3');
798
+ } // Unassigned Code Points
799
+
800
+
801
+ if (opts.allowUnassigned !== true) {
802
+ const hasUnassigned = normalized_map.some(isUnassignedCodePoint);
803
+
804
+ if (hasUnassigned) {
805
+ throw new Error('Unassigned code point, see https://tools.ietf.org/html/rfc4013#section-2.5');
806
+ }
807
+ } // 4. check bidi
808
+
809
+
810
+ const hasBidiRAL = normalized_map.some(isBidirectionalRAL);
811
+ const hasBidiL = normalized_map.some(isBidirectionalL); // 4.1 If a string contains any RandALCat character, the string MUST NOT
812
+ // contain any LCat character.
813
+
814
+ if (hasBidiRAL && hasBidiL) {
815
+ throw new Error('String must not contain RandALCat and LCat at the same time,' + ' see https://tools.ietf.org/html/rfc3454#section-6');
816
+ }
817
+ /**
818
+ * 4.2 If a string contains any RandALCat character, a RandALCat
819
+ * character MUST be the first character of the string, and a
820
+ * RandALCat character MUST be the last character of the string.
821
+ */
822
+
823
+
824
+ const isFirstBidiRAL = isBidirectionalRAL(getCodePoint(first(normalized_input)));
825
+ const isLastBidiRAL = isBidirectionalRAL(getCodePoint(last(normalized_input)));
826
+
827
+ if (hasBidiRAL && !(isFirstBidiRAL && isLastBidiRAL)) {
828
+ 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');
829
+ }
830
+
831
+ return normalized_input;
372
832
  }
373
833
 
374
834
  /*
@@ -384,6 +844,7 @@ class PDFSecurity {
384
844
  if (!info.hasOwnProperty(key)) {
385
845
  continue;
386
846
  }
847
+
387
848
  infoStr += `${key}: ${info[key].toString()}\n`;
388
849
  }
389
850
 
@@ -398,6 +859,7 @@ class PDFSecurity {
398
859
  if (!options.ownerPassword && !options.userPassword) {
399
860
  return null;
400
861
  }
862
+
401
863
  return new PDFSecurity(document, options);
402
864
  }
403
865
 
@@ -407,6 +869,7 @@ class PDFSecurity {
407
869
  }
408
870
 
409
871
  this.document = document;
872
+
410
873
  this._setupEncryption(options);
411
874
  }
412
875
 
@@ -416,13 +879,16 @@ class PDFSecurity {
416
879
  case '1.5':
417
880
  this.version = 2;
418
881
  break;
882
+
419
883
  case '1.6':
420
884
  case '1.7':
421
885
  this.version = 4;
422
886
  break;
887
+
423
888
  case '1.7ext3':
424
889
  this.version = 5;
425
890
  break;
891
+
426
892
  default:
427
893
  this.version = 1;
428
894
  break;
@@ -437,9 +903,12 @@ class PDFSecurity {
437
903
  case 2:
438
904
  case 4:
439
905
  this._setupEncryptionV1V2V4(this.version, encDict, options);
906
+
440
907
  break;
908
+
441
909
  case 5:
442
910
  this._setupEncryptionV5(encDict, options);
911
+
443
912
  break;
444
913
  }
445
914
 
@@ -448,17 +917,20 @@ class PDFSecurity {
448
917
 
449
918
  _setupEncryptionV1V2V4(v, encDict, options) {
450
919
  let r, permissions;
920
+
451
921
  switch (v) {
452
922
  case 1:
453
923
  r = 2;
454
924
  this.keyBits = 40;
455
925
  permissions = getPermissionsR2(options.permissions);
456
926
  break;
927
+
457
928
  case 2:
458
929
  r = 3;
459
930
  this.keyBits = 128;
460
931
  permissions = getPermissionsR3(options.permissions);
461
932
  break;
933
+
462
934
  case 4:
463
935
  r = 4;
464
936
  this.keyBits = 128;
@@ -468,10 +940,10 @@ class PDFSecurity {
468
940
 
469
941
  const paddedUserPassword = processPasswordR2R3R4(options.userPassword);
470
942
  const paddedOwnerPassword = options.ownerPassword ? processPasswordR2R3R4(options.ownerPassword) : paddedUserPassword;
471
-
472
943
  const ownerPasswordEntry = getOwnerPasswordR2R3R4(r, this.keyBits, paddedUserPassword, paddedOwnerPassword);
473
944
  this.encryptionKey = getEncryptionKeyR2R3R4(r, this.keyBits, this.document._id, paddedUserPassword, ownerPasswordEntry, permissions);
474
945
  let userPasswordEntry;
946
+
475
947
  if (r === 2) {
476
948
  userPasswordEntry = getUserPasswordR2(this.encryptionKey);
477
949
  } else {
@@ -479,9 +951,11 @@ class PDFSecurity {
479
951
  }
480
952
 
481
953
  encDict.V = v;
954
+
482
955
  if (v >= 2) {
483
956
  encDict.Length = this.keyBits;
484
957
  }
958
+
485
959
  if (v === 4) {
486
960
  encDict.CF = {
487
961
  StdCF: {
@@ -493,6 +967,7 @@ class PDFSecurity {
493
967
  encDict.StmF = 'StdCF';
494
968
  encDict.StrF = 'StdCF';
495
969
  }
970
+
496
971
  encDict.R = r;
497
972
  encDict.O = wordArrayToBuffer(ownerPasswordEntry);
498
973
  encDict.U = wordArrayToBuffer(userPasswordEntry);
@@ -502,10 +977,8 @@ class PDFSecurity {
502
977
  _setupEncryptionV5(encDict, options) {
503
978
  this.keyBits = 256;
504
979
  const permissions = getPermissionsR3(options);
505
-
506
980
  const processedUserPassword = processPasswordR5(options.userPassword);
507
981
  const processedOwnerPassword = options.ownerPassword ? processPasswordR5(options.ownerPassword) : processedUserPassword;
508
-
509
982
  this.encryptionKey = getEncryptionKeyR5(PDFSecurity.generateRandomWordArray);
510
983
  const userPasswordEntry = getUserPasswordR5(processedUserPassword, PDFSecurity.generateRandomWordArray);
511
984
  const userKeySalt = CryptoJS.lib.WordArray.create(userPasswordEntry.words.slice(10, 12), 8);
@@ -514,7 +987,6 @@ class PDFSecurity {
514
987
  const ownerKeySalt = CryptoJS.lib.WordArray.create(ownerPasswordEntry.words.slice(10, 12), 8);
515
988
  const ownerEncryptionKeyEntry = getOwnerEncryptionKeyR5(processedOwnerPassword, ownerKeySalt, userPasswordEntry, this.encryptionKey);
516
989
  const permsEntry = getEncryptedPermissionsR5(permissions, this.encryptionKey, PDFSecurity.generateRandomWordArray);
517
-
518
990
  encDict.V = 5;
519
991
  encDict.Length = this.keyBits;
520
992
  encDict.CF = {
@@ -537,6 +1009,7 @@ class PDFSecurity {
537
1009
 
538
1010
  getEncryptFn(obj, gen) {
539
1011
  let digest;
1012
+
540
1013
  if (this.version < 5) {
541
1014
  digest = this.encryptionKey.clone().concat(CryptoJS.lib.WordArray.create([(obj & 0xff) << 24 | (obj & 0xff00) << 8 | obj >> 8 & 0xff00 | gen & 0xff, (gen & 0xff00) << 16], 5));
542
1015
  }
@@ -548,6 +1021,7 @@ class PDFSecurity {
548
1021
  }
549
1022
 
550
1023
  let key;
1024
+
551
1025
  if (this.version === 4) {
552
1026
  key = CryptoJS.MD5(digest.concat(CryptoJS.lib.WordArray.create([0x73416c54], 4)));
553
1027
  } else {
@@ -560,58 +1034,72 @@ class PDFSecurity {
560
1034
  padding: CryptoJS.pad.Pkcs7,
561
1035
  iv
562
1036
  };
563
-
564
1037
  return buffer => wordArrayToBuffer(iv.clone().concat(CryptoJS.AES.encrypt(CryptoJS.lib.WordArray.create(buffer), key, options).ciphertext));
565
1038
  }
566
1039
 
567
1040
  end() {
568
1041
  this.dictionary.end();
569
1042
  }
1043
+
570
1044
  }
571
1045
 
572
1046
  function getPermissionsR2(permissionObject = {}) {
573
1047
  let permissions = 0xffffffc0 >> 0;
1048
+
574
1049
  if (permissionObject.printing) {
575
1050
  permissions |= 0b000000000100;
576
1051
  }
1052
+
577
1053
  if (permissionObject.modifying) {
578
1054
  permissions |= 0b000000001000;
579
1055
  }
1056
+
580
1057
  if (permissionObject.copying) {
581
1058
  permissions |= 0b000000010000;
582
1059
  }
1060
+
583
1061
  if (permissionObject.annotating) {
584
1062
  permissions |= 0b000000100000;
585
1063
  }
1064
+
586
1065
  return permissions;
587
1066
  }
588
1067
 
589
1068
  function getPermissionsR3(permissionObject = {}) {
590
1069
  let permissions = 0xfffff0c0 >> 0;
1070
+
591
1071
  if (permissionObject.printing === 'lowResolution') {
592
1072
  permissions |= 0b000000000100;
593
1073
  }
1074
+
594
1075
  if (permissionObject.printing === 'highResolution') {
595
1076
  permissions |= 0b100000000100;
596
1077
  }
1078
+
597
1079
  if (permissionObject.modifying) {
598
1080
  permissions |= 0b000000001000;
599
1081
  }
1082
+
600
1083
  if (permissionObject.copying) {
601
1084
  permissions |= 0b000000010000;
602
1085
  }
1086
+
603
1087
  if (permissionObject.annotating) {
604
1088
  permissions |= 0b000000100000;
605
1089
  }
1090
+
606
1091
  if (permissionObject.fillingForms) {
607
1092
  permissions |= 0b000100000000;
608
1093
  }
1094
+
609
1095
  if (permissionObject.contentAccessibility) {
610
1096
  permissions |= 0b001000000000;
611
1097
  }
1098
+
612
1099
  if (permissionObject.documentAssembly) {
613
1100
  permissions |= 0b010000000000;
614
1101
  }
1102
+
615
1103
  return permissions;
616
1104
  }
617
1105
 
@@ -622,19 +1110,24 @@ function getUserPasswordR2(encryptionKey) {
622
1110
  function getUserPasswordR3R4(documentId, encryptionKey) {
623
1111
  const key = encryptionKey.clone();
624
1112
  let cipher = CryptoJS.MD5(processPasswordR2R3R4().concat(CryptoJS.lib.WordArray.create(documentId)));
1113
+
625
1114
  for (let i = 0; i < 20; i++) {
626
1115
  const xorRound = Math.ceil(key.sigBytes / 4);
1116
+
627
1117
  for (let j = 0; j < xorRound; j++) {
628
1118
  key.words[j] = encryptionKey.words[j] ^ (i | i << 8 | i << 16 | i << 24);
629
1119
  }
1120
+
630
1121
  cipher = CryptoJS.RC4.encrypt(cipher, key).ciphertext;
631
1122
  }
1123
+
632
1124
  return cipher.concat(CryptoJS.lib.WordArray.create(null, 16));
633
1125
  }
634
1126
 
635
1127
  function getOwnerPasswordR2R3R4(r, keyBits, paddedUserPassword, paddedOwnerPassword) {
636
1128
  let digest = paddedOwnerPassword;
637
1129
  let round = r >= 3 ? 51 : 1;
1130
+
638
1131
  for (let i = 0; i < round; i++) {
639
1132
  digest = CryptoJS.MD5(digest);
640
1133
  }
@@ -643,23 +1136,29 @@ function getOwnerPasswordR2R3R4(r, keyBits, paddedUserPassword, paddedOwnerPassw
643
1136
  key.sigBytes = keyBits / 8;
644
1137
  let cipher = paddedUserPassword;
645
1138
  round = r >= 3 ? 20 : 1;
1139
+
646
1140
  for (let i = 0; i < round; i++) {
647
1141
  const xorRound = Math.ceil(key.sigBytes / 4);
1142
+
648
1143
  for (let j = 0; j < xorRound; j++) {
649
1144
  key.words[j] = digest.words[j] ^ (i | i << 8 | i << 16 | i << 24);
650
1145
  }
1146
+
651
1147
  cipher = CryptoJS.RC4.encrypt(cipher, key).ciphertext;
652
1148
  }
1149
+
653
1150
  return cipher;
654
1151
  }
655
1152
 
656
1153
  function getEncryptionKeyR2R3R4(r, keyBits, documentId, paddedUserPassword, ownerPasswordEntry, permissions) {
657
1154
  let key = paddedUserPassword.clone().concat(ownerPasswordEntry).concat(CryptoJS.lib.WordArray.create([lsbFirstWord(permissions)], 4)).concat(CryptoJS.lib.WordArray.create(documentId));
658
1155
  const round = r >= 3 ? 51 : 1;
1156
+
659
1157
  for (let i = 0; i < round; i++) {
660
1158
  key = CryptoJS.MD5(key);
661
1159
  key.sigBytes = keyBits / 8;
662
1160
  }
1161
+
663
1162
  return key;
664
1163
  }
665
1164
 
@@ -712,18 +1211,23 @@ function processPasswordR2R3R4(password = '') {
712
1211
  const out = new Buffer(32);
713
1212
  const length = password.length;
714
1213
  let index = 0;
1214
+
715
1215
  while (index < length && index < 32) {
716
1216
  const code = password.charCodeAt(index);
1217
+
717
1218
  if (code > 0xff) {
718
1219
  throw new Error('Password contains one or more invalid characters.');
719
1220
  }
1221
+
720
1222
  out[index] = code;
721
1223
  index++;
722
1224
  }
1225
+
723
1226
  while (index < 32) {
724
1227
  out[index] = PASSWORD_PADDING[index - length];
725
1228
  index++;
726
1229
  }
1230
+
727
1231
  return CryptoJS.lib.WordArray.create(out);
728
1232
  }
729
1233
 
@@ -745,15 +1249,19 @@ function lsbFirstWord(data) {
745
1249
 
746
1250
  function wordArrayToBuffer(wordArray) {
747
1251
  const byteArray = [];
1252
+
748
1253
  for (let i = 0; i < wordArray.sigBytes; i++) {
749
1254
  byteArray.push(wordArray.words[Math.floor(i / 4)] >> 8 * (3 - i % 4) & 0xff);
750
1255
  }
1256
+
751
1257
  return Buffer.from(byteArray);
752
1258
  }
753
1259
 
754
1260
  const PASSWORD_PADDING = [0x28, 0xbf, 0x4e, 0x5e, 0x4e, 0x75, 0x8a, 0x41, 0x64, 0x00, 0x4e, 0x56, 0xff, 0xfa, 0x01, 0x08, 0x2e, 0x2e, 0x00, 0xb6, 0xd0, 0x68, 0x3e, 0x80, 0x2f, 0x0c, 0xa9, 0xfe, 0x64, 0x53, 0x69, 0x7a];
755
1261
 
756
- const { number } = PDFObject;
1262
+ const {
1263
+ number
1264
+ } = PDFObject;
757
1265
 
758
1266
  class PDFGradient {
759
1267
  constructor(doc) {
@@ -767,6 +1275,7 @@ class PDFGradient {
767
1275
  if (opacity == null) {
768
1276
  opacity = 1;
769
1277
  }
1278
+
770
1279
  color = this.doc._normalizeColor(color);
771
1280
 
772
1281
  if (this.stops.length === 0) {
@@ -795,14 +1304,16 @@ class PDFGradient {
795
1304
 
796
1305
  embed(m) {
797
1306
  let fn;
1307
+
798
1308
  if (this.stops.length === 0) {
799
1309
  return;
800
1310
  }
1311
+
801
1312
  this.embedded = true;
802
- this.matrix = m;
1313
+ this.matrix = m; // if the last stop comes before 100%, add a copy at 100%
803
1314
 
804
- // if the last stop comes before 100%, add a copy at 100%
805
1315
  const last = this.stops[this.stops.length - 1];
1316
+
806
1317
  if (last[0] < 1) {
807
1318
  this.stops.push([1, last[1], last[2]]);
808
1319
  }
@@ -813,6 +1324,7 @@ class PDFGradient {
813
1324
 
814
1325
  for (let i = 0, stopsLength = this.stops.length - 1; i < stopsLength; i++) {
815
1326
  encode.push(0, 1);
1327
+
816
1328
  if (i + 2 !== stopsLength) {
817
1329
  bounds.push(this.stops[i + 1][0]);
818
1330
  }
@@ -824,38 +1336,34 @@ class PDFGradient {
824
1336
  C1: this.stops[i + 1][1],
825
1337
  N: 1
826
1338
  });
827
-
828
1339
  stops.push(fn);
829
1340
  fn.end();
830
- }
1341
+ } // if there are only two stops, we don't need a stitching function
1342
+
831
1343
 
832
- // if there are only two stops, we don't need a stitching function
833
1344
  if (stops.length === 1) {
834
1345
  fn = stops[0];
835
1346
  } else {
836
1347
  fn = this.doc.ref({
837
- FunctionType: 3, // stitching function
1348
+ FunctionType: 3,
1349
+ // stitching function
838
1350
  Domain: [0, 1],
839
1351
  Functions: stops,
840
1352
  Bounds: bounds,
841
1353
  Encode: encode
842
1354
  });
843
-
844
1355
  fn.end();
845
1356
  }
846
1357
 
847
1358
  this.id = `Sh${++this.doc._gradCount}`;
848
-
849
1359
  const shader = this.shader(fn);
850
1360
  shader.end();
851
-
852
1361
  const pattern = this.doc.ref({
853
1362
  Type: 'Pattern',
854
1363
  PatternType: 2,
855
1364
  Shading: shader,
856
1365
  Matrix: this.matrix.map(v => number(v))
857
1366
  });
858
-
859
1367
  pattern.end();
860
1368
 
861
1369
  if (this.stops.some(stop => stop[2] < 1)) {
@@ -867,9 +1375,7 @@ class PDFGradient {
867
1375
  }
868
1376
 
869
1377
  grad = grad.embed(this.matrix);
870
-
871
1378
  const pageBBox = [0, 0, this.doc.page.width, this.doc.page.height];
872
-
873
1379
  const form = this.doc.ref({
874
1380
  Type: 'XObject',
875
1381
  Subtype: 'Form',
@@ -887,10 +1393,8 @@ class PDFGradient {
887
1393
  }
888
1394
  }
889
1395
  });
890
-
891
1396
  form.write('/Pattern cs /Sh1 scn');
892
1397
  form.end(`${pageBBox.join(' ')} re f`);
893
-
894
1398
  const gstate = this.doc.ref({
895
1399
  Type: 'ExtGState',
896
1400
  SMask: {
@@ -899,9 +1403,7 @@ class PDFGradient {
899
1403
  G: form
900
1404
  }
901
1405
  });
902
-
903
1406
  gstate.end();
904
-
905
1407
  const opacityPattern = this.doc.ref({
906
1408
  Type: 'Pattern',
907
1409
  PatternType: 1,
@@ -920,10 +1422,8 @@ class PDFGradient {
920
1422
  }
921
1423
  }
922
1424
  });
923
-
924
1425
  opacityPattern.write('/Gs1 gs /Pattern cs /Sh1 scn');
925
1426
  opacityPattern.end(`${pageBBox.join(' ')} re f`);
926
-
927
1427
  this.doc.page.patterns[this.id] = opacityPattern;
928
1428
  } else {
929
1429
  this.doc.page.patterns[this.id] = pattern;
@@ -941,8 +1441,10 @@ class PDFGradient {
941
1441
  if (!this.embedded || m.join(' ') !== this.matrix.join(' ')) {
942
1442
  this.embed(m);
943
1443
  }
1444
+
944
1445
  return this.doc.addContent(`/${this.id} ${op}`);
945
1446
  }
1447
+
946
1448
  }
947
1449
 
948
1450
  class PDFLinearGradient extends PDFGradient {
@@ -967,6 +1469,7 @@ class PDFLinearGradient extends PDFGradient {
967
1469
  opacityGradient() {
968
1470
  return new PDFLinearGradient(this.doc, this.x1, this.y1, this.x2, this.y2);
969
1471
  }
1472
+
970
1473
  }
971
1474
 
972
1475
  class PDFRadialGradient extends PDFGradient {
@@ -994,12 +1497,20 @@ class PDFRadialGradient extends PDFGradient {
994
1497
  opacityGradient() {
995
1498
  return new PDFRadialGradient(this.doc, this.x1, this.y1, this.r1, this.x2, this.y2, this.r2);
996
1499
  }
997
- }
998
1500
 
999
- var Gradient = { PDFGradient, PDFLinearGradient, PDFRadialGradient };
1501
+ }
1000
1502
 
1001
- const { PDFGradient: PDFGradient$1, PDFLinearGradient: PDFLinearGradient$1, PDFRadialGradient: PDFRadialGradient$1 } = Gradient;
1503
+ var Gradient = {
1504
+ PDFGradient,
1505
+ PDFLinearGradient,
1506
+ PDFRadialGradient
1507
+ };
1002
1508
 
1509
+ const {
1510
+ PDFGradient: PDFGradient$1,
1511
+ PDFLinearGradient: PDFLinearGradient$1,
1512
+ PDFRadialGradient: PDFRadialGradient$1
1513
+ } = Gradient;
1003
1514
  var ColorMixin = {
1004
1515
  initColor() {
1005
1516
  // The opacity dictionaries
@@ -1018,6 +1529,7 @@ var ColorMixin = {
1018
1529
  if (color.length === 4) {
1019
1530
  color = color.replace(/#([0-9A-F])([0-9A-F])([0-9A-F])/i, '#$1$1$2$2$3$3');
1020
1531
  }
1532
+
1021
1533
  const hex = parseInt(color.slice(1), 16);
1022
1534
  color = [hex >> 16, hex >> 8 & 0xff, hex & 0xff];
1023
1535
  } else if (namedColors[color]) {
@@ -1028,11 +1540,11 @@ var ColorMixin = {
1028
1540
  if (Array.isArray(color)) {
1029
1541
  // RGB
1030
1542
  if (color.length === 3) {
1031
- color = color.map(part => part / 255);
1032
- // CMYK
1543
+ color = color.map(part => part / 255); // CMYK
1033
1544
  } else if (color.length === 4) {
1034
1545
  color = color.map(part => part / 100);
1035
1546
  }
1547
+
1036
1548
  return color;
1037
1549
  }
1038
1550
 
@@ -1041,6 +1553,7 @@ var ColorMixin = {
1041
1553
 
1042
1554
  _setColor(color, stroke) {
1043
1555
  color = this._normalizeColor(color);
1556
+
1044
1557
  if (!color) {
1045
1558
  return false;
1046
1559
  }
@@ -1049,9 +1562,11 @@ var ColorMixin = {
1049
1562
 
1050
1563
  if (color instanceof PDFGradient$1) {
1051
1564
  this._setColorSpace('Pattern', stroke);
1565
+
1052
1566
  color.apply(op);
1053
1567
  } else {
1054
1568
  const space = color.length === 4 ? 'DeviceCMYK' : 'DeviceRGB';
1569
+
1055
1570
  this._setColorSpace(space, stroke);
1056
1571
 
1057
1572
  color = color.join(' ');
@@ -1068,41 +1583,48 @@ var ColorMixin = {
1068
1583
 
1069
1584
  fillColor(color, opacity) {
1070
1585
  const set = this._setColor(color, false);
1586
+
1071
1587
  if (set) {
1072
1588
  this.fillOpacity(opacity);
1073
- }
1074
-
1075
- // save this for text wrapper, which needs to reset
1589
+ } // save this for text wrapper, which needs to reset
1076
1590
  // the fill color on new pages
1591
+
1592
+
1077
1593
  this._fillColor = [color, opacity];
1078
1594
  return this;
1079
1595
  },
1080
1596
 
1081
1597
  strokeColor(color, opacity) {
1082
1598
  const set = this._setColor(color, true);
1599
+
1083
1600
  if (set) {
1084
1601
  this.strokeOpacity(opacity);
1085
1602
  }
1603
+
1086
1604
  return this;
1087
1605
  },
1088
1606
 
1089
1607
  opacity(opacity) {
1090
1608
  this._doOpacity(opacity, opacity);
1609
+
1091
1610
  return this;
1092
1611
  },
1093
1612
 
1094
1613
  fillOpacity(opacity) {
1095
1614
  this._doOpacity(opacity, null);
1615
+
1096
1616
  return this;
1097
1617
  },
1098
1618
 
1099
1619
  strokeOpacity(opacity) {
1100
1620
  this._doOpacity(null, opacity);
1621
+
1101
1622
  return this;
1102
1623
  },
1103
1624
 
1104
1625
  _doOpacity(fillOpacity, strokeOpacity) {
1105
1626
  let dictionary, name;
1627
+
1106
1628
  if (fillOpacity == null && strokeOpacity == null) {
1107
1629
  return;
1108
1630
  }
@@ -1110,19 +1632,24 @@ var ColorMixin = {
1110
1632
  if (fillOpacity != null) {
1111
1633
  fillOpacity = Math.max(0, Math.min(1, fillOpacity));
1112
1634
  }
1635
+
1113
1636
  if (strokeOpacity != null) {
1114
1637
  strokeOpacity = Math.max(0, Math.min(1, strokeOpacity));
1115
1638
  }
1639
+
1116
1640
  const key = `${fillOpacity}_${strokeOpacity}`;
1117
1641
 
1118
1642
  if (this._opacityRegistry[key]) {
1119
1643
  [dictionary, name] = this._opacityRegistry[key];
1120
1644
  } else {
1121
- dictionary = { Type: 'ExtGState' };
1645
+ dictionary = {
1646
+ Type: 'ExtGState'
1647
+ };
1122
1648
 
1123
1649
  if (fillOpacity != null) {
1124
1650
  dictionary.ca = fillOpacity;
1125
1651
  }
1652
+
1126
1653
  if (strokeOpacity != null) {
1127
1654
  dictionary.CA = strokeOpacity;
1128
1655
  }
@@ -1145,8 +1672,8 @@ var ColorMixin = {
1145
1672
  radialGradient(x1, y1, r1, x2, y2, r2) {
1146
1673
  return new PDFRadialGradient$1(this, x1, y1, r1, x2, y2, r2);
1147
1674
  }
1148
- };
1149
1675
 
1676
+ };
1150
1677
  var namedColors = {
1151
1678
  aliceblue: [240, 248, 255],
1152
1679
  antiquewhite: [250, 235, 215],
@@ -1298,9 +1825,7 @@ var namedColors = {
1298
1825
  };
1299
1826
 
1300
1827
  let cx, cy, px, py, sx, sy;
1301
-
1302
1828
  cx = cy = px = py = sx = sy = 0;
1303
-
1304
1829
  const parameters = {
1305
1830
  A: 7,
1306
1831
  a: 7,
@@ -1335,13 +1860,17 @@ const parse = function (path) {
1335
1860
  for (let c of path) {
1336
1861
  if (parameters[c] != null) {
1337
1862
  params = parameters[c];
1863
+
1338
1864
  if (cmd) {
1339
1865
  // save existing command
1340
1866
  if (curArg.length > 0) {
1341
1867
  args[args.length] = +curArg;
1342
1868
  }
1343
- ret[ret.length] = { cmd, args };
1344
1869
 
1870
+ ret[ret.length] = {
1871
+ cmd,
1872
+ args
1873
+ };
1345
1874
  args = [];
1346
1875
  curArg = '';
1347
1876
  foundDecimal = false;
@@ -1355,13 +1884,16 @@ const parse = function (path) {
1355
1884
 
1356
1885
  if (args.length === params) {
1357
1886
  // handle reused commands
1358
- ret[ret.length] = { cmd, args };
1359
- args = [+curArg];
1887
+ ret[ret.length] = {
1888
+ cmd,
1889
+ args
1890
+ };
1891
+ args = [+curArg]; // handle assumed commands
1360
1892
 
1361
- // handle assumed commands
1362
1893
  if (cmd === 'M') {
1363
1894
  cmd = 'L';
1364
1895
  }
1896
+
1365
1897
  if (cmd === 'm') {
1366
1898
  cmd = 'l';
1367
1899
  }
@@ -1369,29 +1901,32 @@ const parse = function (path) {
1369
1901
  args[args.length] = +curArg;
1370
1902
  }
1371
1903
 
1372
- foundDecimal = c === '.';
1904
+ foundDecimal = c === '.'; // fix for negative numbers or repeated decimals with no delimeter between commands
1373
1905
 
1374
- // fix for negative numbers or repeated decimals with no delimeter between commands
1375
1906
  curArg = ['-', '.'].includes(c) ? c : '';
1376
1907
  } else {
1377
1908
  curArg += c;
1909
+
1378
1910
  if (c === '.') {
1379
1911
  foundDecimal = true;
1380
1912
  }
1381
1913
  }
1382
- }
1914
+ } // add the last command
1915
+
1383
1916
 
1384
- // add the last command
1385
1917
  if (curArg.length > 0) {
1386
1918
  if (args.length === params) {
1387
1919
  // handle reused commands
1388
- ret[ret.length] = { cmd, args };
1389
- args = [+curArg];
1920
+ ret[ret.length] = {
1921
+ cmd,
1922
+ args
1923
+ };
1924
+ args = [+curArg]; // handle assumed commands
1390
1925
 
1391
- // handle assumed commands
1392
1926
  if (cmd === 'M') {
1393
1927
  cmd = 'L';
1394
1928
  }
1929
+
1395
1930
  if (cmd === 'm') {
1396
1931
  cmd = 'l';
1397
1932
  }
@@ -1400,18 +1935,20 @@ const parse = function (path) {
1400
1935
  }
1401
1936
  }
1402
1937
 
1403
- ret[ret.length] = { cmd, args };
1404
-
1938
+ ret[ret.length] = {
1939
+ cmd,
1940
+ args
1941
+ };
1405
1942
  return ret;
1406
1943
  };
1407
1944
 
1408
1945
  const apply = function (commands, doc) {
1409
1946
  // current point, control point, and subpath starting point
1410
- cx = cy = px = py = sx = sy = 0;
1947
+ cx = cy = px = py = sx = sy = 0; // run the commands
1411
1948
 
1412
- // run the commands
1413
1949
  for (let i = 0; i < commands.length; i++) {
1414
1950
  const c = commands[i];
1951
+
1415
1952
  if (typeof runners[c.cmd] === 'function') {
1416
1953
  runners[c.cmd](doc, c.args);
1417
1954
  }
@@ -1588,6 +2125,7 @@ const runners = {
1588
2125
  cx = sx;
1589
2126
  return cy = sy;
1590
2127
  }
2128
+
1591
2129
  };
1592
2130
 
1593
2131
  const solveArc = function (doc, x, y, coords) {
@@ -1598,9 +2136,9 @@ const solveArc = function (doc, x, y, coords) {
1598
2136
  const bez = segmentToBezier(...(seg || []));
1599
2137
  doc.bezierCurveTo(...(bez || []));
1600
2138
  }
1601
- };
2139
+ }; // from Inkscape svgtopdf, thanks!
2140
+
1602
2141
 
1603
- // from Inkscape svgtopdf, thanks!
1604
2142
  const arcToSegments = function (x, y, rx, ry, large, sweep, rotateX, ox, oy) {
1605
2143
  const th = rotateX * (Math.PI / 180);
1606
2144
  const sin_th = Math.sin(th);
@@ -1610,6 +2148,7 @@ const arcToSegments = function (x, y, rx, ry, large, sweep, rotateX, ox, oy) {
1610
2148
  px = cos_th * (ox - x) * 0.5 + sin_th * (oy - y) * 0.5;
1611
2149
  py = cos_th * (oy - y) * 0.5 - sin_th * (ox - x) * 0.5;
1612
2150
  let pl = px * px / (rx * rx) + py * py / (ry * ry);
2151
+
1613
2152
  if (pl > 1) {
1614
2153
  pl = Math.sqrt(pl);
1615
2154
  rx *= pl;
@@ -1624,24 +2163,25 @@ const arcToSegments = function (x, y, rx, ry, large, sweep, rotateX, ox, oy) {
1624
2163
  const y0 = a10 * ox + a11 * oy;
1625
2164
  const x1 = a00 * x + a01 * y;
1626
2165
  const y1 = a10 * x + a11 * y;
1627
-
1628
2166
  const d = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0);
1629
2167
  let sfactor_sq = 1 / d - 0.25;
2168
+
1630
2169
  if (sfactor_sq < 0) {
1631
2170
  sfactor_sq = 0;
1632
2171
  }
2172
+
1633
2173
  let sfactor = Math.sqrt(sfactor_sq);
2174
+
1634
2175
  if (sweep === large) {
1635
2176
  sfactor = -sfactor;
1636
2177
  }
1637
2178
 
1638
2179
  const xc = 0.5 * (x0 + x1) - sfactor * (y1 - y0);
1639
2180
  const yc = 0.5 * (y0 + y1) + sfactor * (x1 - x0);
1640
-
1641
2181
  const th0 = Math.atan2(y0 - yc, x0 - xc);
1642
2182
  const th1 = Math.atan2(y1 - yc, x1 - xc);
1643
-
1644
2183
  let th_arc = th1 - th0;
2184
+
1645
2185
  if (th_arc < 0 && sweep === 1) {
1646
2186
  th_arc += 2 * Math.PI;
1647
2187
  } else if (th_arc > 0 && sweep === 0) {
@@ -1665,7 +2205,6 @@ const segmentToBezier = function (cx, cy, th0, th1, rx, ry, sin_th, cos_th) {
1665
2205
  const a01 = -sin_th * ry;
1666
2206
  const a10 = sin_th * rx;
1667
2207
  const a11 = cos_th * ry;
1668
-
1669
2208
  const th_half = 0.5 * (th1 - th0);
1670
2209
  const t = 8 / 3 * Math.sin(th_half * 0.5) * Math.sin(th_half * 0.5) / Math.sin(th_half);
1671
2210
  const x1 = cx + Math.cos(th0) - t * Math.sin(th0);
@@ -1674,7 +2213,6 @@ const segmentToBezier = function (cx, cy, th0, th1, rx, ry, sin_th, cos_th) {
1674
2213
  const y3 = cy + Math.sin(th1);
1675
2214
  const x2 = x3 + t * Math.sin(th1);
1676
2215
  const y2 = y3 - t * Math.cos(th1);
1677
-
1678
2216
  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];
1679
2217
  };
1680
2218
 
@@ -1683,22 +2221,26 @@ class SVGPath {
1683
2221
  const commands = parse(path);
1684
2222
  apply(commands, doc);
1685
2223
  }
1686
- }
1687
2224
 
1688
- const { number: number$1 } = PDFObject;
2225
+ }
1689
2226
 
1690
- // This constant is used to approximate a symmetrical arc using a cubic
2227
+ const {
2228
+ number: number$1
2229
+ } = PDFObject; // This constant is used to approximate a symmetrical arc using a cubic
1691
2230
  // Bezier curve.
2231
+
1692
2232
  const KAPPA = 4.0 * ((Math.sqrt(2) - 1.0) / 3.0);
1693
2233
  var VectorMixin = {
1694
2234
  initVector() {
1695
2235
  this._ctm = [1, 0, 0, 1, 0, 0]; // current transformation matrix
2236
+
1696
2237
  return this._ctmStack = [];
1697
2238
  },
1698
2239
 
1699
2240
  save() {
1700
- this._ctmStack.push(this._ctm.slice());
1701
- // TODO: save/restore colorspace and styles so not setting it unnessesarily all the time?
2241
+ this._ctmStack.push(this._ctm.slice()); // TODO: save/restore colorspace and styles so not setting it unnessesarily all the time?
2242
+
2243
+
1702
2244
  return this.addContent('q');
1703
2245
  },
1704
2246
 
@@ -1725,6 +2267,7 @@ var VectorMixin = {
1725
2267
  if (typeof c === 'string') {
1726
2268
  c = this._CAP_STYLES[c.toUpperCase()];
1727
2269
  }
2270
+
1728
2271
  return this.addContent(`${c} J`);
1729
2272
  },
1730
2273
 
@@ -1738,6 +2281,7 @@ var VectorMixin = {
1738
2281
  if (typeof j === 'string') {
1739
2282
  j = this._JOIN_STYLES[j.toUpperCase()];
1740
2283
  }
2284
+
1741
2285
  return this.addContent(`${j} j`);
1742
2286
  },
1743
2287
 
@@ -1745,23 +2289,21 @@ var VectorMixin = {
1745
2289
  return this.addContent(`${number$1(m)} M`);
1746
2290
  },
1747
2291
 
1748
- dash(length, options) {
1749
- let phase;
1750
- if (options == null) {
1751
- options = {};
1752
- }
1753
- if (length == null) {
1754
- return this;
2292
+ dash(length, options = {}) {
2293
+ const originalLength = length;
2294
+
2295
+ if (!Array.isArray(length)) {
2296
+ length = [length, options.space || length];
1755
2297
  }
1756
- if (Array.isArray(length)) {
1757
- length = length.map(v => number$1(v)).join(' ');
1758
- phase = options.phase || 0;
1759
- return this.addContent(`[${length}] ${number$1(phase)} d`);
1760
- } else {
1761
- const space = options.space != null ? options.space : length;
1762
- phase = options.phase || 0;
1763
- return this.addContent(`[${number$1(length)} ${number$1(space)}] ${number$1(phase)} d`);
2298
+
2299
+ const valid = length.every(x => Number.isFinite(x) && x > 0);
2300
+
2301
+ if (!valid) {
2302
+ throw new Error(`dash(${JSON.stringify(originalLength)}, ${JSON.stringify(options)}) invalid, lengths must be numeric and greater than zero`);
1764
2303
  }
2304
+
2305
+ length = length.map(number$1).join(' ');
2306
+ return this.addContent(`[${length}] ${number$1(options.phase || 0)} d`);
1765
2307
  },
1766
2308
 
1767
2309
  undash() {
@@ -1792,11 +2334,10 @@ var VectorMixin = {
1792
2334
  if (r == null) {
1793
2335
  r = 0;
1794
2336
  }
1795
- r = Math.min(r, 0.5 * w, 0.5 * h);
1796
2337
 
1797
- // amount to inset control points from corners (see `ellipse`)
1798
- const c = r * (1.0 - KAPPA);
2338
+ r = Math.min(r, 0.5 * w, 0.5 * h); // amount to inset control points from corners (see `ellipse`)
1799
2339
 
2340
+ const c = r * (1.0 - KAPPA);
1800
2341
  this.moveTo(x + r, y);
1801
2342
  this.lineTo(x + w - r, y);
1802
2343
  this.bezierCurveTo(x + w - c, y, x + w, y + c, x + w, y + r);
@@ -1814,6 +2355,7 @@ var VectorMixin = {
1814
2355
  if (r2 == null) {
1815
2356
  r2 = r1;
1816
2357
  }
2358
+
1817
2359
  x -= r1;
1818
2360
  y -= r2;
1819
2361
  const ox = r1 * KAPPA;
@@ -1822,7 +2364,6 @@ var VectorMixin = {
1822
2364
  const ye = y + r2 * 2;
1823
2365
  const xm = x + r1;
1824
2366
  const ym = y + r2;
1825
-
1826
2367
  this.moveTo(x, ym);
1827
2368
  this.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
1828
2369
  this.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
@@ -1839,9 +2380,9 @@ var VectorMixin = {
1839
2380
  if (anticlockwise == null) {
1840
2381
  anticlockwise = false;
1841
2382
  }
2383
+
1842
2384
  const TWO_PI = 2.0 * Math.PI;
1843
2385
  const HALF_PI = 0.5 * Math.PI;
1844
-
1845
2386
  let deltaAng = endAngle - startAngle;
1846
2387
 
1847
2388
  if (Math.abs(deltaAng) > TWO_PI) {
@@ -1856,40 +2397,32 @@ var VectorMixin = {
1856
2397
  const numSegs = Math.ceil(Math.abs(deltaAng) / HALF_PI);
1857
2398
  const segAng = deltaAng / numSegs;
1858
2399
  const handleLen = segAng / HALF_PI * KAPPA * radius;
1859
- let curAng = startAngle;
2400
+ let curAng = startAngle; // component distances between anchor point and control point
1860
2401
 
1861
- // component distances between anchor point and control point
1862
2402
  let deltaCx = -Math.sin(curAng) * handleLen;
1863
- let deltaCy = Math.cos(curAng) * handleLen;
2403
+ let deltaCy = Math.cos(curAng) * handleLen; // anchor point
1864
2404
 
1865
- // anchor point
1866
2405
  let ax = x + Math.cos(curAng) * radius;
1867
- let ay = y + Math.sin(curAng) * radius;
2406
+ let ay = y + Math.sin(curAng) * radius; // calculate and render segments
1868
2407
 
1869
- // calculate and render segments
1870
2408
  this.moveTo(ax, ay);
1871
2409
 
1872
2410
  for (let segIdx = 0; segIdx < numSegs; segIdx++) {
1873
2411
  // starting control point
1874
2412
  const cp1x = ax + deltaCx;
1875
- const cp1y = ay + deltaCy;
2413
+ const cp1y = ay + deltaCy; // step angle
1876
2414
 
1877
- // step angle
1878
- curAng += segAng;
2415
+ curAng += segAng; // next anchor point
1879
2416
 
1880
- // next anchor point
1881
2417
  ax = x + Math.cos(curAng) * radius;
1882
- ay = y + Math.sin(curAng) * radius;
2418
+ ay = y + Math.sin(curAng) * radius; // next control point delta
1883
2419
 
1884
- // next control point delta
1885
2420
  deltaCx = -Math.sin(curAng) * handleLen;
1886
- deltaCy = Math.cos(curAng) * handleLen;
2421
+ deltaCy = Math.cos(curAng) * handleLen; // ending control point
1887
2422
 
1888
- // ending control point
1889
2423
  const cp2x = ax - deltaCx;
1890
- const cp2y = ay - deltaCy;
2424
+ const cp2y = ay - deltaCy; // render segment
1891
2425
 
1892
- // render segment
1893
2426
  this.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, ax, ay);
1894
2427
  }
1895
2428
 
@@ -1898,9 +2431,11 @@ var VectorMixin = {
1898
2431
 
1899
2432
  polygon(...points) {
1900
2433
  this.moveTo(...(points.shift() || []));
2434
+
1901
2435
  for (let point of points) {
1902
2436
  this.lineTo(...(point || []));
1903
2437
  }
2438
+
1904
2439
  return this.closePath();
1905
2440
  },
1906
2441
 
@@ -1926,6 +2461,7 @@ var VectorMixin = {
1926
2461
  if (color) {
1927
2462
  this.fillColor(color);
1928
2463
  }
2464
+
1929
2465
  return this.addContent(`f${this._windingRule(rule)}`);
1930
2466
  },
1931
2467
 
@@ -1933,6 +2469,7 @@ var VectorMixin = {
1933
2469
  if (color) {
1934
2470
  this.strokeColor(color);
1935
2471
  }
2472
+
1936
2473
  return this.addContent('S');
1937
2474
  },
1938
2475
 
@@ -1940,7 +2477,9 @@ var VectorMixin = {
1940
2477
  if (strokeColor == null) {
1941
2478
  strokeColor = fillColor;
1942
2479
  }
2480
+
1943
2481
  const isFillRule = /(even-?odd)|(non-?zero)/;
2482
+
1944
2483
  if (isFillRule.test(fillColor)) {
1945
2484
  rule = fillColor;
1946
2485
  fillColor = null;
@@ -1973,7 +2512,6 @@ var VectorMixin = {
1973
2512
  m[3] = m1 * m21 + m3 * m22;
1974
2513
  m[4] = m0 * dx + m2 * dy + m4;
1975
2514
  m[5] = m1 * dx + m3 * dy + m5;
1976
-
1977
2515
  const values = [m11, m12, m21, m22, dx, dy].map(v => number$1(v)).join(' ');
1978
2516
  return this.addContent(`${values} cm`);
1979
2517
  },
@@ -1982,11 +2520,8 @@ var VectorMixin = {
1982
2520
  return this.transform(1, 0, 0, 1, x, y);
1983
2521
  },
1984
2522
 
1985
- rotate(angle, options) {
2523
+ rotate(angle, options = {}) {
1986
2524
  let y;
1987
- if (options == null) {
1988
- options = {};
1989
- }
1990
2525
  const rad = angle * Math.PI / 180;
1991
2526
  const cos = Math.cos(rad);
1992
2527
  const sin = Math.sin(rad);
@@ -2003,20 +2538,20 @@ var VectorMixin = {
2003
2538
  return this.transform(cos, sin, -sin, cos, x, y);
2004
2539
  },
2005
2540
 
2006
- scale(xFactor, yFactor, options) {
2541
+ scale(xFactor, yFactor, options = {}) {
2007
2542
  let y;
2543
+
2008
2544
  if (yFactor == null) {
2009
2545
  yFactor = xFactor;
2010
2546
  }
2011
- if (options == null) {
2012
- options = {};
2013
- }
2547
+
2014
2548
  if (typeof yFactor === 'object') {
2015
2549
  options = yFactor;
2016
2550
  yFactor = xFactor;
2017
2551
  }
2018
2552
 
2019
2553
  let x = y = 0;
2554
+
2020
2555
  if (options.origin != null) {
2021
2556
  [x, y] = options.origin;
2022
2557
  x -= xFactor * x;
@@ -2025,6 +2560,7 @@ var VectorMixin = {
2025
2560
 
2026
2561
  return this.transform(xFactor, 0, 0, yFactor, x, y);
2027
2562
  }
2563
+
2028
2564
  };
2029
2565
 
2030
2566
  const WIN_ANSI_MAP = {
@@ -2056,7 +2592,6 @@ const WIN_ANSI_MAP = {
2056
2592
  381: 142,
2057
2593
  382: 158
2058
2594
  };
2059
-
2060
2595
  const characters = `\
2061
2596
  .notdef .notdef .notdef .notdef
2062
2597
  .notdef .notdef .notdef .notdef
@@ -2142,10 +2677,10 @@ class AFMFont {
2142
2677
  this.glyphWidths = {};
2143
2678
  this.boundingBoxes = {};
2144
2679
  this.kernPairs = {};
2680
+ this.parse(); // todo: remove charWidths since appears to not be used
2145
2681
 
2146
- this.parse();
2147
- // todo: remove charWidths since appears to not be used
2148
2682
  this.charWidths = new Array(256);
2683
+
2149
2684
  for (let char = 0; char <= 255; char++) {
2150
2685
  this.charWidths[char] = this.glyphWidths[characters[char]];
2151
2686
  }
@@ -2160,9 +2695,11 @@ class AFMFont {
2160
2695
 
2161
2696
  parse() {
2162
2697
  let section = '';
2698
+
2163
2699
  for (let line of this.contents.split('\n')) {
2164
2700
  var match;
2165
2701
  var a;
2702
+
2166
2703
  if (match = line.match(/^Start(\w+)/)) {
2167
2704
  section = match[1];
2168
2705
  continue;
@@ -2181,25 +2718,30 @@ class AFMFont {
2181
2718
  if (!Array.isArray(a)) {
2182
2719
  a = this.attributes[key] = [a];
2183
2720
  }
2721
+
2184
2722
  a.push(value);
2185
2723
  } else {
2186
2724
  this.attributes[key] = value;
2187
2725
  }
2726
+
2188
2727
  break;
2189
2728
 
2190
2729
  case 'CharMetrics':
2191
2730
  if (!/^CH?\s/.test(line)) {
2192
2731
  continue;
2193
2732
  }
2733
+
2194
2734
  var name = line.match(/\bN\s+(\.?\w+)\s*;/)[1];
2195
2735
  this.glyphWidths[name] = +line.match(/\bWX\s+(\d+)\s*;/)[1];
2196
2736
  break;
2197
2737
 
2198
2738
  case 'KernPairs':
2199
2739
  match = line.match(/^KPX\s+(\.?\w+)\s+(\.?\w+)\s+(-?\d+)/);
2740
+
2200
2741
  if (match) {
2201
2742
  this.kernPairs[match[1] + '\0' + match[2]] = parseInt(match[3]);
2202
2743
  }
2744
+
2203
2745
  break;
2204
2746
  }
2205
2747
  }
@@ -2207,6 +2749,7 @@ class AFMFont {
2207
2749
 
2208
2750
  encodeText(text) {
2209
2751
  const res = [];
2752
+
2210
2753
  for (let i = 0, len = text.length; i < len; i++) {
2211
2754
  let char = text.charCodeAt(i);
2212
2755
  char = WIN_ANSI_MAP[char] || char;
@@ -2250,6 +2793,7 @@ class AFMFont {
2250
2793
 
2251
2794
  return advances;
2252
2795
  }
2796
+
2253
2797
  }
2254
2798
 
2255
2799
  class PDFFont {
@@ -2284,55 +2828,70 @@ class PDFFont {
2284
2828
  if (includeGap == null) {
2285
2829
  includeGap = false;
2286
2830
  }
2831
+
2287
2832
  const gap = includeGap ? this.lineGap : 0;
2288
2833
  return (this.ascender + gap - this.descender) / 1000 * size;
2289
2834
  }
2835
+
2290
2836
  }
2291
2837
 
2292
- // This insanity is so bundlers can inline the font files
2293
2838
  const STANDARD_FONTS = {
2294
2839
  Courier() {
2295
2840
  return fs.readFileSync(__dirname + '/data/Courier.afm', 'utf8');
2296
2841
  },
2842
+
2297
2843
  'Courier-Bold'() {
2298
2844
  return fs.readFileSync(__dirname + '/data/Courier-Bold.afm', 'utf8');
2299
2845
  },
2846
+
2300
2847
  'Courier-Oblique'() {
2301
2848
  return fs.readFileSync(__dirname + '/data/Courier-Oblique.afm', 'utf8');
2302
2849
  },
2850
+
2303
2851
  'Courier-BoldOblique'() {
2304
2852
  return fs.readFileSync(__dirname + '/data/Courier-BoldOblique.afm', 'utf8');
2305
2853
  },
2854
+
2306
2855
  Helvetica() {
2307
2856
  return fs.readFileSync(__dirname + '/data/Helvetica.afm', 'utf8');
2308
2857
  },
2858
+
2309
2859
  'Helvetica-Bold'() {
2310
2860
  return fs.readFileSync(__dirname + '/data/Helvetica-Bold.afm', 'utf8');
2311
2861
  },
2862
+
2312
2863
  'Helvetica-Oblique'() {
2313
2864
  return fs.readFileSync(__dirname + '/data/Helvetica-Oblique.afm', 'utf8');
2314
2865
  },
2866
+
2315
2867
  'Helvetica-BoldOblique'() {
2316
2868
  return fs.readFileSync(__dirname + '/data/Helvetica-BoldOblique.afm', 'utf8');
2317
2869
  },
2870
+
2318
2871
  'Times-Roman'() {
2319
2872
  return fs.readFileSync(__dirname + '/data/Times-Roman.afm', 'utf8');
2320
2873
  },
2874
+
2321
2875
  'Times-Bold'() {
2322
2876
  return fs.readFileSync(__dirname + '/data/Times-Bold.afm', 'utf8');
2323
2877
  },
2878
+
2324
2879
  'Times-Italic'() {
2325
2880
  return fs.readFileSync(__dirname + '/data/Times-Italic.afm', 'utf8');
2326
2881
  },
2882
+
2327
2883
  'Times-BoldItalic'() {
2328
2884
  return fs.readFileSync(__dirname + '/data/Times-BoldItalic.afm', 'utf8');
2329
2885
  },
2886
+
2330
2887
  Symbol() {
2331
2888
  return fs.readFileSync(__dirname + '/data/Symbol.afm', 'utf8');
2332
2889
  },
2890
+
2333
2891
  ZapfDingbats() {
2334
2892
  return fs.readFileSync(__dirname + '/data/ZapfDingbats.afm', 'utf8');
2335
2893
  }
2894
+
2336
2895
  };
2337
2896
 
2338
2897
  class StandardFont extends PDFFont {
@@ -2359,7 +2918,6 @@ class StandardFont extends PDFFont {
2359
2918
  Subtype: 'Type1',
2360
2919
  Encoding: 'WinAnsiEncoding'
2361
2920
  };
2362
-
2363
2921
  return this.dictionary.end();
2364
2922
  }
2365
2923
 
@@ -2368,6 +2926,7 @@ class StandardFont extends PDFFont {
2368
2926
  const glyphs = this.font.glyphsForString(`${text}`);
2369
2927
  const advances = this.font.advancesForGlyphs(glyphs);
2370
2928
  const positions = [];
2929
+
2371
2930
  for (let i = 0; i < glyphs.length; i++) {
2372
2931
  const glyph = glyphs[i];
2373
2932
  positions.push({
@@ -2385,8 +2944,8 @@ class StandardFont extends PDFFont {
2385
2944
  widthOfString(string, size) {
2386
2945
  const glyphs = this.font.glyphsForString(`${string}`);
2387
2946
  const advances = this.font.advancesForGlyphs(glyphs);
2388
-
2389
2947
  let width = 0;
2948
+
2390
2949
  for (let advance of advances) {
2391
2950
  width += advance;
2392
2951
  }
@@ -2398,6 +2957,7 @@ class StandardFont extends PDFFont {
2398
2957
  static isStandardFont(name) {
2399
2958
  return name in STANDARD_FONTS;
2400
2959
  }
2960
+
2401
2961
  }
2402
2962
 
2403
2963
  const toHex = function (num) {
@@ -2413,7 +2973,6 @@ class EmbeddedFont extends PDFFont {
2413
2973
  this.subset = this.font.createSubset();
2414
2974
  this.unicode = [[0]];
2415
2975
  this.widths = [this.font.getGlyph(0).advanceWidth];
2416
-
2417
2976
  this.name = this.font.postscriptName;
2418
2977
  this.scale = 1000 / this.font.unitsPerEm;
2419
2978
  this.ascender = this.font.ascent * this.scale;
@@ -2429,11 +2988,11 @@ class EmbeddedFont extends PDFFont {
2429
2988
  }
2430
2989
 
2431
2990
  layoutRun(text, features) {
2432
- const run = this.font.layout(text, features);
2991
+ const run = this.font.layout(text, features); // Normalize position values
2433
2992
 
2434
- // Normalize position values
2435
2993
  for (let i = 0; i < run.positions.length; i++) {
2436
2994
  const position = run.positions[i];
2995
+
2437
2996
  for (let key in position) {
2438
2997
  position[key] *= this.scale;
2439
2998
  }
@@ -2448,7 +3007,9 @@ class EmbeddedFont extends PDFFont {
2448
3007
  if (!this.layoutCache) {
2449
3008
  return this.layoutRun(text);
2450
3009
  }
3010
+
2451
3011
  let cached;
3012
+
2452
3013
  if (cached = this.layoutCache[text]) {
2453
3014
  return cached;
2454
3015
  }
@@ -2466,16 +3027,18 @@ class EmbeddedFont extends PDFFont {
2466
3027
 
2467
3028
  let glyphs = onlyWidth ? null : [];
2468
3029
  let positions = onlyWidth ? null : [];
2469
- let advanceWidth = 0;
2470
-
2471
- // Split the string by words to increase cache efficiency.
3030
+ let advanceWidth = 0; // Split the string by words to increase cache efficiency.
2472
3031
  // For this purpose, spaces and tabs are a good enough delimeter.
3032
+
2473
3033
  let last = 0;
2474
3034
  let index = 0;
3035
+
2475
3036
  while (index <= text.length) {
2476
3037
  var needle;
3038
+
2477
3039
  if (index === text.length && last < index || (needle = text.charAt(index), [' ', '\t'].includes(needle))) {
2478
3040
  const run = this.layoutCached(text.slice(last, ++index));
3041
+
2479
3042
  if (!onlyWidth) {
2480
3043
  glyphs = glyphs.concat(run.glyphs);
2481
3044
  positions = positions.concat(run.positions);
@@ -2488,13 +3051,20 @@ class EmbeddedFont extends PDFFont {
2488
3051
  }
2489
3052
  }
2490
3053
 
2491
- return { glyphs, positions, advanceWidth };
3054
+ return {
3055
+ glyphs,
3056
+ positions,
3057
+ advanceWidth
3058
+ };
2492
3059
  }
2493
3060
 
2494
3061
  encode(text, features) {
2495
- const { glyphs, positions } = this.layout(text, features);
2496
-
3062
+ const {
3063
+ glyphs,
3064
+ positions
3065
+ } = this.layout(text, features);
2497
3066
  const res = [];
3067
+
2498
3068
  for (let i = 0; i < glyphs.length; i++) {
2499
3069
  const glyph = glyphs[i];
2500
3070
  const gid = this.subset.includeGlyph(glyph.id);
@@ -2503,6 +3073,7 @@ class EmbeddedFont extends PDFFont {
2503
3073
  if (this.widths[gid] == null) {
2504
3074
  this.widths[gid] = glyph.advanceWidth * this.scale;
2505
3075
  }
3076
+
2506
3077
  if (this.unicode[gid] == null) {
2507
3078
  this.unicode[gid] = glyph.codePoints;
2508
3079
  }
@@ -2526,28 +3097,33 @@ class EmbeddedFont extends PDFFont {
2526
3097
  }
2527
3098
 
2528
3099
  this.subset.encodeStream().on('data', data => fontFile.write(data)).on('end', () => fontFile.end());
2529
-
2530
3100
  const familyClass = ((this.font['OS/2'] != null ? this.font['OS/2'].sFamilyClass : undefined) || 0) >> 8;
2531
3101
  let flags = 0;
3102
+
2532
3103
  if (this.font.post.isFixedPitch) {
2533
3104
  flags |= 1 << 0;
2534
3105
  }
3106
+
2535
3107
  if (1 <= familyClass && familyClass <= 7) {
2536
3108
  flags |= 1 << 1;
2537
3109
  }
3110
+
2538
3111
  flags |= 1 << 2; // assume the font uses non-latin characters
3112
+
2539
3113
  if (familyClass === 10) {
2540
3114
  flags |= 1 << 3;
2541
3115
  }
3116
+
2542
3117
  if (this.font.head.macStyle.italic) {
2543
3118
  flags |= 1 << 6;
2544
- }
3119
+ } // generate a tag (6 uppercase letters. 16 is the char code offset from '1' to 'A'. 74 will map to 'Z')
3120
+
2545
3121
 
2546
- // generate a tag (6 uppercase letters. 16 is the char code offset from '1' to 'A'. 74 will map to 'Z')
2547
3122
  const tag = [1, 2, 3, 4, 5, 6].map(i => String.fromCharCode((this.id.charCodeAt(i) || 74) + 16)).join('');
2548
3123
  const name = tag + '+' + this.font.postscriptName;
2549
-
2550
- const { bbox } = this.font;
3124
+ const {
3125
+ bbox
3126
+ } = this.font;
2551
3127
  const descriptor = this.document.ref({
2552
3128
  Type: 'FontDescriptor',
2553
3129
  FontName: name,
@@ -2568,7 +3144,6 @@ class EmbeddedFont extends PDFFont {
2568
3144
  }
2569
3145
 
2570
3146
  descriptor.end();
2571
-
2572
3147
  const descendantFont = this.document.ref({
2573
3148
  Type: 'Font',
2574
3149
  Subtype: isCFF ? 'CIDFontType0' : 'CIDFontType2',
@@ -2581,9 +3156,7 @@ class EmbeddedFont extends PDFFont {
2581
3156
  FontDescriptor: descriptor,
2582
3157
  W: [0, this.widths]
2583
3158
  });
2584
-
2585
3159
  descendantFont.end();
2586
-
2587
3160
  this.dictionary.data = {
2588
3161
  Type: 'Font',
2589
3162
  Subtype: 'Type0',
@@ -2592,21 +3165,19 @@ class EmbeddedFont extends PDFFont {
2592
3165
  DescendantFonts: [descendantFont],
2593
3166
  ToUnicode: this.toUnicodeCmap()
2594
3167
  };
2595
-
2596
3168
  return this.dictionary.end();
2597
- }
2598
-
2599
- // Maps the glyph ids encoded in the PDF back to unicode strings
3169
+ } // Maps the glyph ids encoded in the PDF back to unicode strings
2600
3170
  // Because of ligature substitutions and the like, there may be one or more
2601
3171
  // unicode characters represented by each glyph.
3172
+
3173
+
2602
3174
  toUnicodeCmap() {
2603
3175
  const cmap = this.document.ref();
2604
-
2605
3176
  const entries = [];
3177
+
2606
3178
  for (let codePoints of this.unicode) {
2607
- const encoded = [];
3179
+ const encoded = []; // encode codePoints to utf16
2608
3180
 
2609
- // encode codePoints to utf16
2610
3181
  for (let value of codePoints) {
2611
3182
  if (value > 0xffff) {
2612
3183
  value -= 0x10000;
@@ -2642,14 +3213,15 @@ CMapName currentdict /CMap defineresource pop
2642
3213
  end
2643
3214
  end\
2644
3215
  `);
2645
-
2646
3216
  return cmap;
2647
3217
  }
3218
+
2648
3219
  }
2649
3220
 
2650
3221
  class PDFFontFactory {
2651
3222
  static open(document, src, family, id) {
2652
3223
  let font;
3224
+
2653
3225
  if (typeof src === 'string') {
2654
3226
  if (StandardFont.isStandardFont(src)) {
2655
3227
  return new StandardFont(document, src, id);
@@ -2657,6 +3229,7 @@ class PDFFontFactory {
2657
3229
 
2658
3230
  src = fs.readFileSync(src);
2659
3231
  }
3232
+
2660
3233
  if (Buffer.isBuffer(src)) {
2661
3234
  font = fontkit.create(src, family);
2662
3235
  } else if (src instanceof Uint8Array) {
@@ -2671,21 +3244,19 @@ class PDFFontFactory {
2671
3244
 
2672
3245
  return new EmbeddedFont(document, font, id);
2673
3246
  }
3247
+
2674
3248
  }
2675
3249
 
2676
3250
  var FontsMixin = {
2677
3251
  initFonts(defaultFont = 'Helvetica') {
2678
3252
  // Lookup table for embedded fonts
2679
3253
  this._fontFamilies = {};
2680
- this._fontCount = 0;
3254
+ this._fontCount = 0; // Font state
2681
3255
 
2682
- // Font state
2683
3256
  this._fontSize = 12;
2684
3257
  this._font = null;
3258
+ this._registeredFonts = {}; // Set the default font
2685
3259
 
2686
- this._registeredFonts = {};
2687
-
2688
- // Set the default font
2689
3260
  if (defaultFont) {
2690
3261
  this.font(defaultFont);
2691
3262
  }
@@ -2693,17 +3264,22 @@ var FontsMixin = {
2693
3264
 
2694
3265
  font(src, family, size) {
2695
3266
  let cacheKey, font;
3267
+
2696
3268
  if (typeof family === 'number') {
2697
3269
  size = family;
2698
3270
  family = null;
2699
- }
3271
+ } // check registered fonts if src is a string
3272
+
2700
3273
 
2701
- // check registered fonts if src is a string
2702
3274
  if (typeof src === 'string' && this._registeredFonts[src]) {
2703
3275
  cacheKey = src;
2704
- ({ src, family } = this._registeredFonts[src]);
3276
+ ({
3277
+ src,
3278
+ family
3279
+ } = this._registeredFonts[src]);
2705
3280
  } else {
2706
3281
  cacheKey = family || src;
3282
+
2707
3283
  if (typeof cacheKey !== 'string') {
2708
3284
  cacheKey = null;
2709
3285
  }
@@ -2711,26 +3287,25 @@ var FontsMixin = {
2711
3287
 
2712
3288
  if (size != null) {
2713
3289
  this.fontSize(size);
2714
- }
3290
+ } // fast path: check if the font is already in the PDF
3291
+
2715
3292
 
2716
- // fast path: check if the font is already in the PDF
2717
3293
  if (font = this._fontFamilies[cacheKey]) {
2718
3294
  this._font = font;
2719
3295
  return this;
2720
- }
3296
+ } // load the font
2721
3297
 
2722
- // load the font
2723
- const id = `F${++this._fontCount}`;
2724
- this._font = PDFFontFactory.open(this, src, family, id);
2725
3298
 
2726
- // check for existing font familes with the same name already in the PDF
3299
+ const id = `F${++this._fontCount}`;
3300
+ this._font = PDFFontFactory.open(this, src, family, id); // check for existing font familes with the same name already in the PDF
2727
3301
  // useful if the font was passed as a buffer
3302
+
2728
3303
  if (font = this._fontFamilies[this._font.name]) {
2729
3304
  this._font = font;
2730
3305
  return this;
2731
- }
3306
+ } // save the font for reuse later
3307
+
2732
3308
 
2733
- // save the font for reuse later
2734
3309
  if (cacheKey) {
2735
3310
  this._fontFamilies[cacheKey] = this._font;
2736
3311
  }
@@ -2751,6 +3326,7 @@ var FontsMixin = {
2751
3326
  if (includeGap == null) {
2752
3327
  includeGap = false;
2753
3328
  }
3329
+
2754
3330
  return this._font.lineHeight(this._fontSize, includeGap);
2755
3331
  },
2756
3332
 
@@ -2759,9 +3335,9 @@ var FontsMixin = {
2759
3335
  src,
2760
3336
  family
2761
3337
  };
2762
-
2763
3338
  return this;
2764
3339
  }
3340
+
2765
3341
  };
2766
3342
 
2767
3343
  class LineWrapper extends events.EventEmitter {
@@ -2773,6 +3349,7 @@ class LineWrapper extends events.EventEmitter {
2773
3349
  this.wordSpacing = options.wordSpacing === 0;
2774
3350
  this.columns = options.columns || 1;
2775
3351
  this.columnGap = options.columnGap != null ? options.columnGap : 18; // 1/4 inch
3352
+
2776
3353
  this.lineWidth = (options.width - this.columnGap * (this.columns - 1)) / this.columns;
2777
3354
  this.spaceLeft = this.lineWidth;
2778
3355
  this.startX = this.document.x;
@@ -2780,17 +3357,16 @@ class LineWrapper extends events.EventEmitter {
2780
3357
  this.column = 1;
2781
3358
  this.ellipsis = options.ellipsis;
2782
3359
  this.continuedX = 0;
2783
- this.features = options.features;
3360
+ this.features = options.features; // calculate the maximum Y position the text can appear at
2784
3361
 
2785
- // calculate the maximum Y position the text can appear at
2786
3362
  if (options.height != null) {
2787
3363
  this.height = options.height;
2788
3364
  this.maxY = this.startY + options.height;
2789
3365
  } else {
2790
3366
  this.maxY = this.document.page.maxY();
2791
- }
3367
+ } // handle paragraph indents
3368
+
2792
3369
 
2793
- // handle paragraph indents
2794
3370
  this.on('firstLine', options => {
2795
3371
  // if this is the first line of the text segment, and
2796
3372
  // we're continuing where we left off, indent that much
@@ -2798,27 +3374,30 @@ class LineWrapper extends events.EventEmitter {
2798
3374
  const indent = this.continuedX || this.indent;
2799
3375
  this.document.x += indent;
2800
3376
  this.lineWidth -= indent;
2801
-
2802
3377
  return this.once('line', () => {
2803
3378
  this.document.x -= indent;
2804
3379
  this.lineWidth += indent;
3380
+
2805
3381
  if (options.continued && !this.continuedX) {
2806
3382
  this.continuedX = this.indent;
2807
3383
  }
3384
+
2808
3385
  if (!options.continued) {
2809
3386
  return this.continuedX = 0;
2810
3387
  }
2811
3388
  });
2812
- });
3389
+ }); // handle left aligning last lines of paragraphs
2813
3390
 
2814
- // handle left aligning last lines of paragraphs
2815
3391
  this.on('lastLine', options => {
2816
- const { align } = options;
3392
+ const {
3393
+ align
3394
+ } = options;
3395
+
2817
3396
  if (align === 'justify') {
2818
3397
  options.align = 'left';
2819
3398
  }
2820
- this.lastLine = true;
2821
3399
 
3400
+ this.lastLine = true;
2822
3401
  return this.once('line', () => {
2823
3402
  this.document.y += options.paragraphGap || 0;
2824
3403
  options.align = align;
@@ -2841,10 +3420,9 @@ class LineWrapper extends events.EventEmitter {
2841
3420
  while (bk = breaker.nextBreak()) {
2842
3421
  var shouldContinue;
2843
3422
  let word = text.slice((last != null ? last.position : undefined) || 0, bk.position);
2844
- let w = wordWidths[word] != null ? wordWidths[word] : wordWidths[word] = this.wordWidth(word);
2845
-
2846
- // if the word is longer than the whole line, chop it up
3423
+ let w = wordWidths[word] != null ? wordWidths[word] : wordWidths[word] = this.wordWidth(word); // if the word is longer than the whole line, chop it up
2847
3424
  // TODO: break by grapheme clusters, not JS string characters
3425
+
2848
3426
  if (w > this.lineWidth + this.continuedX) {
2849
3427
  // make some fake break objects
2850
3428
  let lbk = last;
@@ -2853,6 +3431,7 @@ class LineWrapper extends events.EventEmitter {
2853
3431
  while (word.length) {
2854
3432
  // fit as much of the word as possible into the space we have
2855
3433
  var l, mightGrow;
3434
+
2856
3435
  if (w > this.spaceLeft) {
2857
3436
  // start our check at the end of our available space - this method is faster than a loop of each character and it resolves
2858
3437
  // an issue with long loops when processing massive words, such as a huge number of spaces
@@ -2862,8 +3441,9 @@ class LineWrapper extends events.EventEmitter {
2862
3441
  } else {
2863
3442
  l = word.length;
2864
3443
  }
2865
- let mustShrink = w > this.spaceLeft && l > 0;
2866
- // shrink or grow word as necessary after our near-guess above
3444
+
3445
+ let mustShrink = w > this.spaceLeft && l > 0; // shrink or grow word as necessary after our near-guess above
3446
+
2867
3447
  while (mustShrink || mightGrow) {
2868
3448
  if (mustShrink) {
2869
3449
  w = this.wordWidth(word.slice(0, --l));
@@ -2873,14 +3453,15 @@ class LineWrapper extends events.EventEmitter {
2873
3453
  mustShrink = w > this.spaceLeft && l > 0;
2874
3454
  mightGrow = w <= this.spaceLeft && l < word.length;
2875
3455
  }
2876
- }
3456
+ } // send a required break unless this is the last piece and a linebreak is not specified
3457
+
2877
3458
 
2878
- // send a required break unless this is the last piece and a linebreak is not specified
2879
3459
  fbk.required = bk.required || l < word.length;
2880
3460
  shouldContinue = fn(word.slice(0, l), w, fbk, lbk);
2881
- lbk = { required: false };
3461
+ lbk = {
3462
+ required: false
3463
+ }; // get the remaining piece of the word
2882
3464
 
2883
- // get the remaining piece of the word
2884
3465
  word = word.slice(l);
2885
3466
  w = this.wordWidth(word);
2886
3467
 
@@ -2896,6 +3477,7 @@ class LineWrapper extends events.EventEmitter {
2896
3477
  if (shouldContinue === false) {
2897
3478
  break;
2898
3479
  }
3480
+
2899
3481
  last = bk;
2900
3482
  }
2901
3483
  }
@@ -2905,20 +3487,24 @@ class LineWrapper extends events.EventEmitter {
2905
3487
  if (options.indent != null) {
2906
3488
  this.indent = options.indent;
2907
3489
  }
3490
+
2908
3491
  if (options.characterSpacing != null) {
2909
3492
  this.characterSpacing = options.characterSpacing;
2910
3493
  }
3494
+
2911
3495
  if (options.wordSpacing != null) {
2912
3496
  this.wordSpacing = options.wordSpacing;
2913
3497
  }
3498
+
2914
3499
  if (options.ellipsis != null) {
2915
3500
  this.ellipsis = options.ellipsis;
2916
- }
2917
-
2918
- // make sure we're actually on the page
3501
+ } // make sure we're actually on the page
2919
3502
  // and that the first line of is never by
2920
3503
  // itself at the bottom of a page (orphans)
3504
+
3505
+
2921
3506
  const nextY = this.document.y + this.document.currentLineHeight(true);
3507
+
2922
3508
  if (this.document.y > this.maxY || nextY > this.maxY) {
2923
3509
  this.nextSection();
2924
3510
  }
@@ -2927,19 +3513,22 @@ class LineWrapper extends events.EventEmitter {
2927
3513
  let textWidth = 0;
2928
3514
  let wc = 0;
2929
3515
  let lc = 0;
3516
+ let {
3517
+ y
3518
+ } = this.document; // used to reset Y pos if options.continued (below)
2930
3519
 
2931
- let { y } = this.document; // used to reset Y pos if options.continued (below)
2932
3520
  const emitLine = () => {
2933
3521
  options.textWidth = textWidth + this.wordSpacing * (wc - 1);
2934
3522
  options.wordCount = wc;
2935
3523
  options.lineWidth = this.lineWidth;
2936
- ({ y } = this.document);
3524
+ ({
3525
+ y
3526
+ } = this.document);
2937
3527
  this.emit('line', buffer, options, this);
2938
3528
  return lc++;
2939
3529
  };
2940
3530
 
2941
3531
  this.emit('sectionStart', options, this);
2942
-
2943
3532
  this.eachWord(text, (word, w, bk, last) => {
2944
3533
  if (last == null || last.required) {
2945
3534
  this.emit('firstLine', options, this);
@@ -2956,20 +3545,23 @@ class LineWrapper extends events.EventEmitter {
2956
3545
  // if the user specified a max height and an ellipsis, and is about to pass the
2957
3546
  // max height and max columns after the next line, append the ellipsis
2958
3547
  const lh = this.document.currentLineHeight(true);
3548
+
2959
3549
  if (this.height != null && this.ellipsis && this.document.y + lh * 2 > this.maxY && this.column >= this.columns) {
2960
3550
  if (this.ellipsis === true) {
2961
3551
  this.ellipsis = '…';
2962
3552
  } // map default ellipsis character
2963
- buffer = buffer.replace(/\s+$/, '');
2964
- textWidth = this.wordWidth(buffer + this.ellipsis);
2965
3553
 
2966
- // remove characters from the buffer until the ellipsis fits
3554
+
3555
+ buffer = buffer.replace(/\s+$/, '');
3556
+ textWidth = this.wordWidth(buffer + this.ellipsis); // remove characters from the buffer until the ellipsis fits
2967
3557
  // to avoid inifinite loop need to stop while-loop if buffer is empty string
3558
+
2968
3559
  while (buffer && textWidth > this.lineWidth) {
2969
3560
  buffer = buffer.slice(0, -1).replace(/\s+$/, '');
2970
3561
  textWidth = this.wordWidth(buffer + this.ellipsis);
2971
- }
2972
- // need to add ellipsis only if there is enough space for it
3562
+ } // need to add ellipsis only if there is enough space for it
3563
+
3564
+
2973
3565
  if (textWidth <= this.lineWidth) {
2974
3566
  buffer = buffer + this.ellipsis;
2975
3567
  }
@@ -2988,22 +3580,20 @@ class LineWrapper extends events.EventEmitter {
2988
3580
  this.emit('lastLine', options, this);
2989
3581
  }
2990
3582
 
2991
- emitLine();
2992
-
2993
- // if we've reached the edge of the page,
3583
+ emitLine(); // if we've reached the edge of the page,
2994
3584
  // continue on a new page or column
3585
+
2995
3586
  if (this.document.y + lh > this.maxY) {
2996
- const shouldContinue = this.nextSection();
3587
+ const shouldContinue = this.nextSection(); // stop if we reached the maximum height
2997
3588
 
2998
- // stop if we reached the maximum height
2999
3589
  if (!shouldContinue) {
3000
3590
  wc = 0;
3001
3591
  buffer = '';
3002
3592
  return false;
3003
3593
  }
3004
- }
3594
+ } // reset the space left and buffer
3595
+
3005
3596
 
3006
- // reset the space left and buffer
3007
3597
  if (bk.required) {
3008
3598
  this.spaceLeft = this.lineWidth;
3009
3599
  buffer = '';
@@ -3026,15 +3616,15 @@ class LineWrapper extends events.EventEmitter {
3026
3616
  emitLine();
3027
3617
  }
3028
3618
 
3029
- this.emit('sectionEnd', options, this);
3030
-
3031
- // if the wrap is set to be continued, save the X position
3619
+ this.emit('sectionEnd', options, this); // if the wrap is set to be continued, save the X position
3032
3620
  // to start the first line of the next segment at, and reset
3033
3621
  // the y position
3622
+
3034
3623
  if (options.continued === true) {
3035
3624
  if (lc > 1) {
3036
3625
  this.continuedX = 0;
3037
3626
  }
3627
+
3038
3628
  this.continuedX += options.textWidth || 0;
3039
3629
  return this.document.y = y;
3040
3630
  } else {
@@ -3057,9 +3647,11 @@ class LineWrapper extends events.EventEmitter {
3057
3647
  this.startY = this.document.page.margins.top;
3058
3648
  this.maxY = this.document.page.maxY();
3059
3649
  this.document.x = this.startX;
3650
+
3060
3651
  if (this.document._fillColor) {
3061
3652
  this.document.fillColor(...(this.document._fillColor || []));
3062
3653
  }
3654
+
3063
3655
  this.emit('pageBreak', options, this);
3064
3656
  } else {
3065
3657
  this.document.x += this.lineWidth + this.columnGap;
@@ -3070,14 +3662,16 @@ class LineWrapper extends events.EventEmitter {
3070
3662
  this.emit('sectionStart', options, this);
3071
3663
  return true;
3072
3664
  }
3073
- }
3074
3665
 
3075
- const { number: number$2 } = PDFObject;
3666
+ }
3076
3667
 
3668
+ const {
3669
+ number: number$2
3670
+ } = PDFObject;
3077
3671
  var TextMixin = {
3078
3672
  initText() {
3079
- this._line = this._line.bind(this);
3080
- // Current coordinates
3673
+ this._line = this._line.bind(this); // Current coordinates
3674
+
3081
3675
  this.x = 0;
3082
3676
  this.y = 0;
3083
3677
  return this._lineGap = 0;
@@ -3092,6 +3686,7 @@ var TextMixin = {
3092
3686
  if (lines == null) {
3093
3687
  lines = 1;
3094
3688
  }
3689
+
3095
3690
  this.y += this.currentLineHeight(true) * lines + this._lineGap;
3096
3691
  return this;
3097
3692
  },
@@ -3100,24 +3695,24 @@ var TextMixin = {
3100
3695
  if (lines == null) {
3101
3696
  lines = 1;
3102
3697
  }
3698
+
3103
3699
  this.y -= this.currentLineHeight(true) * lines + this._lineGap;
3104
3700
  return this;
3105
3701
  },
3106
3702
 
3107
3703
  _text(text, x, y, options, lineCallback) {
3108
- options = this._initOptions(x, y, options);
3704
+ options = this._initOptions(x, y, options); // Convert text to a string
3109
3705
 
3110
- // Convert text to a string
3111
- text = text == null ? '' : `${text}`;
3706
+ text = text == null ? '' : `${text}`; // if the wordSpacing option is specified, remove multiple consecutive spaces
3112
3707
 
3113
- // if the wordSpacing option is specified, remove multiple consecutive spaces
3114
3708
  if (options.wordSpacing) {
3115
3709
  text = text.replace(/\s{2,}/g, ' ');
3116
- }
3710
+ } // word wrapping
3711
+
3117
3712
 
3118
- // word wrapping
3119
3713
  if (options.width) {
3120
3714
  let wrapper = this._wrapper;
3715
+
3121
3716
  if (!wrapper) {
3122
3717
  wrapper = new LineWrapper(this, options);
3123
3718
  wrapper.on('line', lineCallback);
@@ -3125,9 +3720,7 @@ var TextMixin = {
3125
3720
 
3126
3721
  this._wrapper = options.continued ? wrapper : null;
3127
3722
  this._textOptions = options.continued ? options : null;
3128
- wrapper.wrap(text, options);
3129
-
3130
- // render paragraphs as single lines
3723
+ wrapper.wrap(text, options); // render paragraphs as single lines
3131
3724
  } else {
3132
3725
  for (let line of text.split('\n')) {
3133
3726
  lineCallback(line, options);
@@ -3141,20 +3734,20 @@ var TextMixin = {
3141
3734
  return this._text(text, x, y, options, this._line);
3142
3735
  },
3143
3736
 
3144
- widthOfString(string, options) {
3145
- if (options == null) {
3146
- options = {};
3147
- }
3737
+ widthOfString(string, options = {}) {
3148
3738
  return this._font.widthOfString(string, this._fontSize, options.features) + (options.characterSpacing || 0) * (string.length - 1);
3149
3739
  },
3150
3740
 
3151
3741
  heightOfString(text, options) {
3152
- const { x, y } = this;
3153
-
3742
+ const {
3743
+ x,
3744
+ y
3745
+ } = this;
3154
3746
  options = this._initOptions(options);
3155
3747
  options.height = Infinity; // don't break pages
3156
3748
 
3157
3749
  const lineGap = options.lineGap || this._lineGap || 0;
3750
+
3158
3751
  this._text(text, this.x, this.y, options, () => {
3159
3752
  return this.y += this.currentLineHeight(true) + lineGap;
3160
3753
  });
@@ -3162,20 +3755,17 @@ var TextMixin = {
3162
3755
  const height = this.y - y;
3163
3756
  this.x = x;
3164
3757
  this.y = y;
3165
-
3166
3758
  return height;
3167
3759
  },
3168
3760
 
3169
3761
  list(list, x, y, options, wrapper) {
3170
3762
  options = this._initOptions(x, y, options);
3171
-
3172
3763
  const listType = options.listType || 'bullet';
3173
3764
  const unit = Math.round(this._font.ascender / 1000 * this._fontSize);
3174
3765
  const midLine = unit / 2;
3175
3766
  const r = options.bulletRadius || unit / 3;
3176
3767
  const indent = options.textIndent || (listType === 'bullet' ? r * 5 : unit * 2);
3177
3768
  const itemIndent = options.bulletIndent || (listType === 'bullet' ? r * 8 : unit * 2);
3178
-
3179
3769
  let level = 1;
3180
3770
  const items = [];
3181
3771
  const levels = [];
@@ -3183,8 +3773,10 @@ var TextMixin = {
3183
3773
 
3184
3774
  var flatten = function (list) {
3185
3775
  let n = 1;
3776
+
3186
3777
  for (let i = 0; i < list.length; i++) {
3187
3778
  const item = list[i];
3779
+
3188
3780
  if (Array.isArray(item)) {
3189
3781
  level++;
3190
3782
  flatten(item);
@@ -3192,6 +3784,7 @@ var TextMixin = {
3192
3784
  } else {
3193
3785
  items.push(item);
3194
3786
  levels.push(level);
3787
+
3195
3788
  if (listType !== 'bullet') {
3196
3789
  numbers.push(n++);
3197
3790
  }
@@ -3205,6 +3798,7 @@ var TextMixin = {
3205
3798
  switch (listType) {
3206
3799
  case 'numbered':
3207
3800
  return `${n}.`;
3801
+
3208
3802
  case 'lettered':
3209
3803
  var letter = String.fromCharCode((n - 1) % 26 + 65);
3210
3804
  var times = Math.floor((n - 1) / 26 + 1);
@@ -3215,11 +3809,11 @@ var TextMixin = {
3215
3809
 
3216
3810
  wrapper = new LineWrapper(this, options);
3217
3811
  wrapper.on('line', this._line);
3218
-
3219
3812
  level = 1;
3220
3813
  let i = 0;
3221
3814
  wrapper.on('firstLine', () => {
3222
3815
  let l;
3816
+
3223
3817
  if ((l = levels[i++]) !== level) {
3224
3818
  const diff = itemIndent * (l - level);
3225
3819
  this.x += diff;
@@ -3231,66 +3825,58 @@ var TextMixin = {
3231
3825
  case 'bullet':
3232
3826
  this.circle(this.x - indent + r, this.y + midLine, r);
3233
3827
  return this.fill();
3828
+
3234
3829
  case 'numbered':
3235
3830
  case 'lettered':
3236
3831
  var text = label(numbers[i - 1]);
3237
3832
  return this._fragment(text, this.x - indent, this.y, options);
3238
3833
  }
3239
3834
  });
3240
-
3241
3835
  wrapper.on('sectionStart', () => {
3242
3836
  const pos = indent + itemIndent * (level - 1);
3243
3837
  this.x += pos;
3244
3838
  return wrapper.lineWidth -= pos;
3245
3839
  });
3246
-
3247
3840
  wrapper.on('sectionEnd', () => {
3248
3841
  const pos = indent + itemIndent * (level - 1);
3249
3842
  this.x -= pos;
3250
3843
  return wrapper.lineWidth += pos;
3251
3844
  });
3252
-
3253
3845
  wrapper.wrap(items.join('\n'), options);
3254
-
3255
3846
  return this;
3256
3847
  },
3257
3848
 
3258
- _initOptions(x, y, options) {
3259
- if (x == null) {
3260
- x = {};
3261
- }
3262
- if (options == null) {
3263
- options = {};
3264
- }
3849
+ _initOptions(x = {}, y, options = {}) {
3265
3850
  if (typeof x === 'object') {
3266
3851
  options = x;
3267
3852
  x = null;
3268
- }
3853
+ } // clone options object
3269
3854
 
3270
- // clone options object
3271
- const result = Object.assign({}, options);
3272
3855
 
3273
- // extend options with previous values for continued text
3856
+ const result = Object.assign({}, options); // extend options with previous values for continued text
3857
+
3274
3858
  if (this._textOptions) {
3275
3859
  for (let key in this._textOptions) {
3276
3860
  const val = this._textOptions[key];
3861
+
3277
3862
  if (key !== 'continued') {
3278
3863
  if (result[key] == null) {
3279
3864
  result[key] = val;
3280
3865
  }
3281
3866
  }
3282
3867
  }
3283
- }
3868
+ } // Update the current position
3869
+
3284
3870
 
3285
- // Update the current position
3286
3871
  if (x != null) {
3287
3872
  this.x = x;
3288
3873
  }
3874
+
3289
3875
  if (y != null) {
3290
3876
  this.y = y;
3291
- }
3877
+ } // wrap to margins if no x or y position passed
3878
+
3292
3879
 
3293
- // wrap to margins if no x or y position passed
3294
3880
  if (result.lineBreak !== false) {
3295
3881
  if (result.width == null) {
3296
3882
  result.width = this.page.width - this.x - this.page.margins.right;
@@ -3300,18 +3886,18 @@ var TextMixin = {
3300
3886
  if (!result.columns) {
3301
3887
  result.columns = 0;
3302
3888
  }
3889
+
3303
3890
  if (result.columnGap == null) {
3304
3891
  result.columnGap = 18;
3305
3892
  } // 1/4 inch
3306
3893
 
3894
+
3307
3895
  return result;
3308
3896
  },
3309
3897
 
3310
- _line(text, options, wrapper) {
3311
- if (options == null) {
3312
- options = {};
3313
- }
3898
+ _line(text, options = {}, wrapper) {
3314
3899
  this._fragment(text, this.x, this.y, options);
3900
+
3315
3901
  const lineGap = options.lineGap || this._lineGap || 0;
3316
3902
 
3317
3903
  if (!wrapper) {
@@ -3324,16 +3910,16 @@ var TextMixin = {
3324
3910
  _fragment(text, x, y, options) {
3325
3911
  let dy, encoded, i, positions, textWidth, words;
3326
3912
  text = `${text}`.replace(/\n/g, '');
3913
+
3327
3914
  if (text.length === 0) {
3328
3915
  return;
3329
- }
3916
+ } // handle options
3917
+
3330
3918
 
3331
- // handle options
3332
3919
  const align = options.align || 'left';
3333
3920
  let wordSpacing = options.wordSpacing || 0;
3334
- const characterSpacing = options.characterSpacing || 0;
3921
+ const characterSpacing = options.characterSpacing || 0; // text alignments
3335
3922
 
3336
- // text alignments
3337
3923
  if (options.width) {
3338
3924
  switch (align) {
3339
3925
  case 'right':
@@ -3353,9 +3939,9 @@ var TextMixin = {
3353
3939
  wordSpacing = Math.max(0, (options.lineWidth - textWidth) / Math.max(1, words.length - 1) - spaceWidth);
3354
3940
  break;
3355
3941
  }
3356
- }
3942
+ } // text baseline alignments based on http://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling
3943
+
3357
3944
 
3358
- // text baseline alignments based on http://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling
3359
3945
  if (typeof options.baseline === 'number') {
3360
3946
  dy = -options.baseline;
3361
3947
  } else {
@@ -3363,52 +3949,68 @@ var TextMixin = {
3363
3949
  case 'svg-middle':
3364
3950
  dy = 0.5 * this._font.xHeight;
3365
3951
  break;
3952
+
3366
3953
  case 'middle':
3367
3954
  case 'svg-central':
3368
3955
  dy = 0.5 * (this._font.descender + this._font.ascender);
3369
3956
  break;
3957
+
3370
3958
  case 'bottom':
3371
3959
  case 'ideographic':
3372
3960
  dy = this._font.descender;
3373
3961
  break;
3962
+
3374
3963
  case 'alphabetic':
3375
3964
  dy = 0;
3376
3965
  break;
3966
+
3377
3967
  case 'mathematical':
3378
3968
  dy = 0.5 * this._font.ascender;
3379
3969
  break;
3970
+
3380
3971
  case 'hanging':
3381
3972
  dy = 0.8 * this._font.ascender;
3382
3973
  break;
3974
+
3383
3975
  case 'top':
3384
3976
  dy = this._font.ascender;
3385
3977
  break;
3978
+
3386
3979
  default:
3387
3980
  dy = this._font.ascender;
3388
3981
  }
3982
+
3389
3983
  dy = dy / 1000 * this._fontSize;
3390
- }
3984
+ } // calculate the actual rendered width of the string after word and character spacing
3391
3985
 
3392
- // calculate the actual rendered width of the string after word and character spacing
3393
- const renderedWidth = options.textWidth + wordSpacing * (options.wordCount - 1) + characterSpacing * (text.length - 1);
3394
3986
 
3395
- // create link annotations if the link option is given
3987
+ const renderedWidth = options.textWidth + wordSpacing * (options.wordCount - 1) + characterSpacing * (text.length - 1); // create link annotations if the link option is given
3988
+
3396
3989
  if (options.link != null) {
3397
3990
  this.link(x, y, renderedWidth, this.currentLineHeight(), options.link);
3398
3991
  }
3399
3992
 
3400
- // create underline or strikethrough line
3993
+ if (options.goTo != null) {
3994
+ this.goTo(x, y, renderedWidth, this.currentLineHeight(), options.goTo);
3995
+ }
3996
+
3997
+ if (options.destination != null) {
3998
+ this.addNamedDestination(options.destination, 'XYZ', x, y, null);
3999
+ } // create underline or strikethrough line
4000
+
4001
+
3401
4002
  if (options.underline || options.strike) {
3402
4003
  this.save();
4004
+
3403
4005
  if (!options.stroke) {
3404
4006
  this.strokeColor(...(this._fillColor || []));
3405
4007
  }
3406
4008
 
3407
4009
  const lineWidth = this._fontSize < 10 ? 0.5 : Math.floor(this._fontSize / 10);
3408
4010
  this.lineWidth(lineWidth);
3409
-
3410
4011
  const d = options.underline ? 1 : 2;
3411
4012
  let lineY = y + this.currentLineHeight() / d;
4013
+
3412
4014
  if (options.underline) {
3413
4015
  lineY -= lineWidth;
3414
4016
  }
@@ -3419,74 +4021,74 @@ var TextMixin = {
3419
4021
  this.restore();
3420
4022
  }
3421
4023
 
3422
- this.save();
4024
+ this.save(); // oblique (angle in degrees or boolean)
3423
4025
 
3424
- // oblique (angle in degrees or boolean)
3425
4026
  if (options.oblique) {
3426
4027
  let skew;
4028
+
3427
4029
  if (typeof options.oblique === 'number') {
3428
4030
  skew = -Math.tan(options.oblique * Math.PI / 180);
3429
4031
  } else {
3430
4032
  skew = -0.25;
3431
4033
  }
4034
+
3432
4035
  this.transform(1, 0, 0, 1, x, y);
3433
4036
  this.transform(1, 0, skew, 1, -skew * dy, 0);
3434
4037
  this.transform(1, 0, 0, 1, -x, -y);
3435
- }
4038
+ } // flip coordinate system
4039
+
3436
4040
 
3437
- // flip coordinate system
3438
4041
  this.transform(1, 0, 0, -1, 0, this.page.height);
3439
- y = this.page.height - y - dy;
4042
+ y = this.page.height - y - dy; // add current font to page if necessary
3440
4043
 
3441
- // add current font to page if necessary
3442
4044
  if (this.page.fonts[this._font.id] == null) {
3443
4045
  this.page.fonts[this._font.id] = this._font.ref();
3444
- }
4046
+ } // begin the text object
3445
4047
 
3446
- // begin the text object
3447
- this.addContent('BT');
3448
4048
 
3449
- // text position
3450
- this.addContent(`1 0 0 1 ${number$2(x)} ${number$2(y)} Tm`);
4049
+ this.addContent('BT'); // text position
3451
4050
 
3452
- // font and font size
3453
- this.addContent(`/${this._font.id} ${number$2(this._fontSize)} Tf`);
4051
+ this.addContent(`1 0 0 1 ${number$2(x)} ${number$2(y)} Tm`); // font and font size
4052
+
4053
+ this.addContent(`/${this._font.id} ${number$2(this._fontSize)} Tf`); // rendering mode
3454
4054
 
3455
- // rendering mode
3456
4055
  const mode = options.fill && options.stroke ? 2 : options.stroke ? 1 : 0;
4056
+
3457
4057
  if (mode) {
3458
4058
  this.addContent(`${mode} Tr`);
3459
- }
4059
+ } // Character spacing
4060
+
3460
4061
 
3461
- // Character spacing
3462
4062
  if (characterSpacing) {
3463
4063
  this.addContent(`${number$2(characterSpacing)} Tc`);
3464
- }
3465
-
3466
- // Add the actual text
4064
+ } // Add the actual text
3467
4065
  // If we have a word spacing value, we need to encode each word separately
3468
4066
  // since the normal Tw operator only works on character code 32, which isn't
3469
4067
  // used for embedded fonts.
4068
+
4069
+
3470
4070
  if (wordSpacing) {
3471
4071
  words = text.trim().split(/\s+/);
3472
4072
  wordSpacing += this.widthOfString(' ') + characterSpacing;
3473
4073
  wordSpacing *= 1000 / this._fontSize;
3474
-
3475
4074
  encoded = [];
3476
4075
  positions = [];
4076
+
3477
4077
  for (let word of words) {
3478
4078
  const [encodedWord, positionsWord] = this._font.encode(word, options.features);
3479
- encoded = encoded.concat(encodedWord);
3480
- positions = positions.concat(positionsWord);
3481
4079
 
3482
- // add the word spacing to the end of the word
4080
+ encoded = encoded.concat(encodedWord);
4081
+ positions = positions.concat(positionsWord); // add the word spacing to the end of the word
3483
4082
  // clone object because of cache
4083
+
3484
4084
  const space = {};
3485
4085
  const object = positions[positions.length - 1];
4086
+
3486
4087
  for (let key in object) {
3487
4088
  const val = object[key];
3488
4089
  space[key] = val;
3489
4090
  }
4091
+
3490
4092
  space.xAdvance += wordSpacing;
3491
4093
  positions[positions.length - 1] = space;
3492
4094
  }
@@ -3497,9 +4099,8 @@ var TextMixin = {
3497
4099
  const scale = this._fontSize / 1000;
3498
4100
  const commands = [];
3499
4101
  let last = 0;
3500
- let hadOffset = false;
4102
+ let hadOffset = false; // Adds a segment of text to the TJ command buffer
3501
4103
 
3502
- // Adds a segment of text to the TJ command buffer
3503
4104
  const addSegment = cur => {
3504
4105
  if (last < cur) {
3505
4106
  const hex = encoded.slice(last, cur).join('');
@@ -3508,9 +4109,9 @@ var TextMixin = {
3508
4109
  }
3509
4110
 
3510
4111
  return last = cur;
3511
- };
4112
+ }; // Flushes the current TJ commands to the output stream
4113
+
3512
4114
 
3513
- // Flushes the current TJ commands to the output stream
3514
4115
  const flush = i => {
3515
4116
  addSegment(i);
3516
4117
 
@@ -3524,44 +4125,41 @@ var TextMixin = {
3524
4125
  // If we have an x or y offset, we have to break out of the current TJ command
3525
4126
  // so we can move the text position.
3526
4127
  const pos = positions[i];
4128
+
3527
4129
  if (pos.xOffset || pos.yOffset) {
3528
4130
  // Flush the current buffer
3529
- flush(i);
4131
+ flush(i); // Move the text position and flush just the current character
3530
4132
 
3531
- // Move the text position and flush just the current character
3532
4133
  this.addContent(`1 0 0 1 ${number$2(x + pos.xOffset * scale)} ${number$2(y + pos.yOffset * scale)} Tm`);
3533
4134
  flush(i + 1);
3534
-
3535
4135
  hadOffset = true;
3536
4136
  } else {
3537
4137
  // If the last character had an offset, reset the text position
3538
4138
  if (hadOffset) {
3539
4139
  this.addContent(`1 0 0 1 ${number$2(x)} ${number$2(y)} Tm`);
3540
4140
  hadOffset = false;
3541
- }
4141
+ } // Group segments that don't have any advance adjustments
4142
+
3542
4143
 
3543
- // Group segments that don't have any advance adjustments
3544
4144
  if (pos.xAdvance - pos.advanceWidth !== 0) {
3545
4145
  addSegment(i + 1);
3546
4146
  }
3547
4147
  }
3548
4148
 
3549
4149
  x += pos.xAdvance * scale;
3550
- }
4150
+ } // Flush any remaining commands
3551
4151
 
3552
- // Flush any remaining commands
3553
- flush(i);
3554
4152
 
3555
- // end the text object
3556
- this.addContent('ET');
4153
+ flush(i); // end the text object
4154
+
4155
+ this.addContent('ET'); // restore flipped coordinate system
3557
4156
 
3558
- // restore flipped coordinate system
3559
4157
  return this.restore();
3560
4158
  }
4159
+
3561
4160
  };
3562
4161
 
3563
4162
  const MARKERS = [0xffc0, 0xffc1, 0xffc2, 0xffc3, 0xffc5, 0xffc6, 0xffc7, 0xffc8, 0xffc9, 0xffca, 0xffcb, 0xffcc, 0xffcd, 0xffce, 0xffcf];
3564
-
3565
4163
  const COLOR_SPACE_MAP = {
3566
4164
  1: 'DeviceGray',
3567
4165
  3: 'DeviceRGB',
@@ -3573,35 +4171,36 @@ class JPEG {
3573
4171
  let marker;
3574
4172
  this.data = data;
3575
4173
  this.label = label;
4174
+
3576
4175
  if (this.data.readUInt16BE(0) !== 0xffd8) {
3577
4176
  throw 'SOI not found in JPEG';
3578
4177
  }
3579
4178
 
3580
4179
  let pos = 2;
4180
+
3581
4181
  while (pos < this.data.length) {
3582
4182
  marker = this.data.readUInt16BE(pos);
3583
4183
  pos += 2;
4184
+
3584
4185
  if (MARKERS.includes(marker)) {
3585
4186
  break;
3586
4187
  }
4188
+
3587
4189
  pos += this.data.readUInt16BE(pos);
3588
4190
  }
3589
4191
 
3590
4192
  if (!MARKERS.includes(marker)) {
3591
4193
  throw 'Invalid JPEG.';
3592
4194
  }
3593
- pos += 2;
3594
4195
 
4196
+ pos += 2;
3595
4197
  this.bits = this.data[pos++];
3596
4198
  this.height = this.data.readUInt16BE(pos);
3597
4199
  pos += 2;
3598
-
3599
4200
  this.width = this.data.readUInt16BE(pos);
3600
4201
  pos += 2;
3601
-
3602
4202
  const channels = this.data[pos++];
3603
4203
  this.colorSpace = COLOR_SPACE_MAP[channels];
3604
-
3605
4204
  this.obj = null;
3606
4205
  }
3607
4206
 
@@ -3618,20 +4217,19 @@ class JPEG {
3618
4217
  Height: this.height,
3619
4218
  ColorSpace: this.colorSpace,
3620
4219
  Filter: 'DCTDecode'
3621
- });
3622
-
3623
- // add extra decode params for CMYK images. By swapping the
4220
+ }); // add extra decode params for CMYK images. By swapping the
3624
4221
  // min and max values from the default, we invert the colors. See
3625
4222
  // section 4.8.4 of the spec.
4223
+
3626
4224
  if (this.colorSpace === 'DeviceCMYK') {
3627
4225
  this.obj.data['Decode'] = [1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0];
3628
4226
  }
3629
4227
 
3630
- this.obj.end(this.data);
4228
+ this.obj.end(this.data); // free memory
3631
4229
 
3632
- // free memory
3633
4230
  return this.data = null;
3634
4231
  }
4232
+
3635
4233
  }
3636
4234
 
3637
4235
  class PNGImage {
@@ -3646,12 +4244,12 @@ class PNGImage {
3646
4244
 
3647
4245
  embed(document) {
3648
4246
  this.document = document;
4247
+
3649
4248
  if (this.obj) {
3650
4249
  return;
3651
4250
  }
3652
4251
 
3653
4252
  const hasAlphaChannel = this.image.hasAlphaChannel;
3654
-
3655
4253
  this.obj = this.document.ref({
3656
4254
  Type: 'XObject',
3657
4255
  Subtype: 'Image',
@@ -3668,7 +4266,6 @@ class PNGImage {
3668
4266
  BitsPerComponent: this.image.bits,
3669
4267
  Columns: this.width
3670
4268
  });
3671
-
3672
4269
  this.obj.data['DecodeParms'] = params;
3673
4270
  params.end();
3674
4271
  }
@@ -3678,14 +4275,13 @@ class PNGImage {
3678
4275
  } else {
3679
4276
  // embed the color palette in the PDF as an object stream
3680
4277
  const palette = this.document.ref();
3681
- palette.end(new Buffer(this.image.palette));
4278
+ palette.end(new Buffer(this.image.palette)); // build the color space array for the image
3682
4279
 
3683
- // build the color space array for the image
3684
4280
  this.obj.data['ColorSpace'] = ['Indexed', 'DeviceRGB', this.image.palette.length / 3 - 1, palette];
3685
- }
3686
-
3687
- // For PNG color types 0, 2 and 3, the transparency data is stored in
4281
+ } // For PNG color types 0, 2 and 3, the transparency data is stored in
3688
4282
  // a dedicated PNG chunk.
4283
+
4284
+
3689
4285
  if (this.image.transparency.grayscale != null) {
3690
4286
  // Use Color Key Masking (spec section 4.8.5)
3691
4287
  // An array with N elements, where N is two times the number of color components.
@@ -3694,8 +4290,11 @@ class PNGImage {
3694
4290
  } else if (this.image.transparency.rgb) {
3695
4291
  // Use Color Key Masking (spec section 4.8.5)
3696
4292
  // An array with N elements, where N is two times the number of color components.
3697
- const { rgb } = this.image.transparency;
4293
+ const {
4294
+ rgb
4295
+ } = this.image.transparency;
3698
4296
  const mask = [];
4297
+
3699
4298
  for (let x of rgb) {
3700
4299
  mask.push(x, x);
3701
4300
  }
@@ -3711,6 +4310,7 @@ class PNGImage {
3711
4310
  // SMask object and store it separately in the PDF.
3712
4311
  return this.splitAlphaChannel();
3713
4312
  }
4313
+
3714
4314
  this.finalize();
3715
4315
  }
3716
4316
 
@@ -3726,15 +4326,13 @@ class PNGImage {
3726
4326
  ColorSpace: 'DeviceGray',
3727
4327
  Decode: [0, 1]
3728
4328
  });
3729
-
3730
4329
  sMask.end(this.alphaChannel);
3731
4330
  this.obj.data['SMask'] = sMask;
3732
- }
4331
+ } // add the actual image data
4332
+
3733
4333
 
3734
- // add the actual image data
3735
- this.obj.end(this.imgData);
4334
+ this.obj.end(this.imgData); // free memory
3736
4335
 
3737
- // free memory
3738
4336
  this.image = null;
3739
4337
  return this.imgData = null;
3740
4338
  }
@@ -3746,16 +4344,17 @@ class PNGImage {
3746
4344
  const pixelCount = this.width * this.height;
3747
4345
  const imgData = new Buffer(pixelCount * colorCount);
3748
4346
  const alphaChannel = new Buffer(pixelCount);
3749
-
3750
4347
  let i = p = a = 0;
3751
- const len = pixels.length;
3752
- // For 16bit images copy only most significant byte (MSB) - PNG data is always stored in network byte order (MSB first)
4348
+ const len = pixels.length; // For 16bit images copy only most significant byte (MSB) - PNG data is always stored in network byte order (MSB first)
4349
+
3753
4350
  const skipByteCount = this.image.bits === 16 ? 1 : 0;
4351
+
3754
4352
  while (i < len) {
3755
4353
  for (let colorIndex = 0; colorIndex < colorCount; colorIndex++) {
3756
4354
  imgData[p++] = pixels[i++];
3757
4355
  i += skipByteCount;
3758
4356
  }
4357
+
3759
4358
  alphaChannel[a++] = pixels[i++];
3760
4359
  i += skipByteCount;
3761
4360
  }
@@ -3770,8 +4369,8 @@ class PNGImage {
3770
4369
  const transparency = this.image.transparency.indexed;
3771
4370
  return this.image.decodePixels(pixels => {
3772
4371
  const alphaChannel = new Buffer(this.width * this.height);
3773
-
3774
4372
  let i = 0;
4373
+
3775
4374
  for (let j = 0, end = pixels.length; j < end; j++) {
3776
4375
  alphaChannel[i++] = transparency[pixels[j]];
3777
4376
  }
@@ -3780,6 +4379,7 @@ class PNGImage {
3780
4379
  return this.finalize();
3781
4380
  });
3782
4381
  }
4382
+
3783
4383
  }
3784
4384
 
3785
4385
  /*
@@ -3790,16 +4390,19 @@ By Devon Govett
3790
4390
  class PDFImage {
3791
4391
  static open(src, label) {
3792
4392
  let data;
4393
+
3793
4394
  if (Buffer.isBuffer(src)) {
3794
4395
  data = src;
3795
4396
  } else if (src instanceof ArrayBuffer) {
3796
4397
  data = new Buffer(new Uint8Array(src));
3797
4398
  } else {
3798
4399
  let match;
4400
+
3799
4401
  if (match = /^data:.+;base64,(.*)$/.exec(src)) {
3800
4402
  data = new Buffer(match[1], 'base64');
3801
4403
  } else {
3802
4404
  data = fs.readFileSync(src);
4405
+
3803
4406
  if (!data) {
3804
4407
  return;
3805
4408
  }
@@ -3814,6 +4417,7 @@ class PDFImage {
3814
4417
  throw new Error('Unknown image format.');
3815
4418
  }
3816
4419
  }
4420
+
3817
4421
  }
3818
4422
 
3819
4423
  var ImagesMixin = {
@@ -3822,11 +4426,9 @@ var ImagesMixin = {
3822
4426
  return this._imageCount = 0;
3823
4427
  },
3824
4428
 
3825
- image(src, x, y, options) {
4429
+ image(src, x, y, options = {}) {
3826
4430
  let bh, bp, bw, image, ip, left, left1;
3827
- if (options == null) {
3828
- options = {};
3829
- }
4431
+
3830
4432
  if (typeof x === 'object') {
3831
4433
  options = x;
3832
4434
  x = null;
@@ -3873,6 +4475,7 @@ var ImagesMixin = {
3873
4475
  [bw, bh] = options.fit;
3874
4476
  bp = bw / bh;
3875
4477
  ip = image.width / image.height;
4478
+
3876
4479
  if (ip > bp) {
3877
4480
  w = bw;
3878
4481
  h = bw / ip;
@@ -3884,6 +4487,7 @@ var ImagesMixin = {
3884
4487
  [bw, bh] = options.cover;
3885
4488
  bp = bw / bh;
3886
4489
  ip = image.width / image.height;
4490
+
3887
4491
  if (ip > bp) {
3888
4492
  h = bh;
3889
4493
  w = bh * ip;
@@ -3905,9 +4509,22 @@ var ImagesMixin = {
3905
4509
  } else if (options.valign === 'bottom') {
3906
4510
  y = y + bh - h;
3907
4511
  }
4512
+ } // create link annotations if the link option is given
4513
+
4514
+
4515
+ if (options.link != null) {
4516
+ this.link(x, y, w, h, options.link);
4517
+ }
4518
+
4519
+ if (options.goTo != null) {
4520
+ this.goTo(x, y, w, h, options.goTo);
3908
4521
  }
3909
4522
 
3910
- // Set the current y position to below the image if it is in the document flow
4523
+ if (options.destination != null) {
4524
+ this.addNamedDestination(options.destination, 'XYZ', x, y, null);
4525
+ } // Set the current y position to below the image if it is in the document flow
4526
+
4527
+
3911
4528
  if (this.y === y) {
3912
4529
  this.y += h;
3913
4530
  }
@@ -3916,18 +4533,19 @@ var ImagesMixin = {
3916
4533
  this.transform(w, 0, 0, -h, x, y + h);
3917
4534
  this.addContent(`/${image.label} Do`);
3918
4535
  this.restore();
3919
-
3920
4536
  return this;
3921
4537
  },
3922
4538
 
3923
4539
  openImage(src) {
3924
4540
  let image;
4541
+
3925
4542
  if (typeof src === 'string') {
3926
4543
  image = this._imageRegistry[src];
3927
4544
  }
3928
4545
 
3929
4546
  if (!image) {
3930
4547
  image = PDFImage.open(src, `I${++this._imageCount}`);
4548
+
3931
4549
  if (typeof src === 'string') {
3932
4550
  this._imageRegistry[src] = image;
3933
4551
  }
@@ -3935,6 +4553,7 @@ var ImagesMixin = {
3935
4553
 
3936
4554
  return image;
3937
4555
  }
4556
+
3938
4557
  };
3939
4558
 
3940
4559
  var AnnotationsMixin = {
@@ -3942,18 +4561,21 @@ var AnnotationsMixin = {
3942
4561
  options.Type = 'Annot';
3943
4562
  options.Rect = this._convertRect(x, y, w, h);
3944
4563
  options.Border = [0, 0, 0];
4564
+
3945
4565
  if (options.Subtype !== 'Link') {
3946
4566
  if (options.C == null) {
3947
4567
  options.C = this._normalizeColor(options.color || [0, 0, 0]);
3948
4568
  }
3949
4569
  } // convert colors
4570
+
4571
+
3950
4572
  delete options.color;
3951
4573
 
3952
4574
  if (typeof options.Dest === 'string') {
3953
4575
  options.Dest = new String(options.Dest);
3954
- }
4576
+ } // Capitalize keys
4577
+
3955
4578
 
3956
- // Capitalize keys
3957
4579
  for (let key in options) {
3958
4580
  const val = options[key];
3959
4581
  options[key[0].toUpperCase() + key.slice(1)] = val;
@@ -3965,28 +4587,35 @@ var AnnotationsMixin = {
3965
4587
  return this;
3966
4588
  },
3967
4589
 
3968
- note(x, y, w, h, contents, options) {
3969
- if (options == null) {
3970
- options = {};
3971
- }
4590
+ note(x, y, w, h, contents, options = {}) {
3972
4591
  options.Subtype = 'Text';
3973
4592
  options.Contents = new String(contents);
3974
4593
  options.Name = 'Comment';
4594
+
3975
4595
  if (options.color == null) {
3976
4596
  options.color = [243, 223, 92];
3977
4597
  }
4598
+
3978
4599
  return this.annotate(x, y, w, h, options);
3979
4600
  },
3980
4601
 
3981
- link(x, y, w, h, url, options) {
3982
- if (options == null) {
3983
- options = {};
3984
- }
4602
+ goTo(x, y, w, h, name, options = {}) {
4603
+ options.Subtype = 'Link';
4604
+ options.A = this.ref({
4605
+ S: 'GoTo',
4606
+ D: new String(name)
4607
+ });
4608
+ options.A.end();
4609
+ return this.annotate(x, y, w, h, options);
4610
+ },
4611
+
4612
+ link(x, y, w, h, url, options = {}) {
3985
4613
  options.Subtype = 'Link';
3986
4614
 
3987
4615
  if (typeof url === 'number') {
3988
4616
  // Link to a page in the document (the page must already exist)
3989
4617
  const pages = this._root.data.Pages.data;
4618
+
3990
4619
  if (url >= 0 && url < pages.Kids.length) {
3991
4620
  options.A = this.ref({
3992
4621
  S: 'GoTo',
@@ -4008,75 +4637,54 @@ var AnnotationsMixin = {
4008
4637
  return this.annotate(x, y, w, h, options);
4009
4638
  },
4010
4639
 
4011
- _markup(x, y, w, h, options) {
4012
- if (options == null) {
4013
- options = {};
4014
- }
4640
+ _markup(x, y, w, h, options = {}) {
4015
4641
  const [x1, y1, x2, y2] = this._convertRect(x, y, w, h);
4642
+
4016
4643
  options.QuadPoints = [x1, y2, x2, y2, x1, y1, x2, y1];
4017
4644
  options.Contents = new String();
4018
4645
  return this.annotate(x, y, w, h, options);
4019
4646
  },
4020
4647
 
4021
- highlight(x, y, w, h, options) {
4022
- if (options == null) {
4023
- options = {};
4024
- }
4648
+ highlight(x, y, w, h, options = {}) {
4025
4649
  options.Subtype = 'Highlight';
4650
+
4026
4651
  if (options.color == null) {
4027
4652
  options.color = [241, 238, 148];
4028
4653
  }
4654
+
4029
4655
  return this._markup(x, y, w, h, options);
4030
4656
  },
4031
4657
 
4032
- underline(x, y, w, h, options) {
4033
- if (options == null) {
4034
- options = {};
4035
- }
4658
+ underline(x, y, w, h, options = {}) {
4036
4659
  options.Subtype = 'Underline';
4037
4660
  return this._markup(x, y, w, h, options);
4038
4661
  },
4039
4662
 
4040
- strike(x, y, w, h, options) {
4041
- if (options == null) {
4042
- options = {};
4043
- }
4663
+ strike(x, y, w, h, options = {}) {
4044
4664
  options.Subtype = 'StrikeOut';
4045
4665
  return this._markup(x, y, w, h, options);
4046
4666
  },
4047
4667
 
4048
- lineAnnotation(x1, y1, x2, y2, options) {
4049
- if (options == null) {
4050
- options = {};
4051
- }
4668
+ lineAnnotation(x1, y1, x2, y2, options = {}) {
4052
4669
  options.Subtype = 'Line';
4053
4670
  options.Contents = new String();
4054
4671
  options.L = [x1, this.page.height - y1, x2, this.page.height - y2];
4055
4672
  return this.annotate(x1, y1, x2, y2, options);
4056
4673
  },
4057
4674
 
4058
- rectAnnotation(x, y, w, h, options) {
4059
- if (options == null) {
4060
- options = {};
4061
- }
4675
+ rectAnnotation(x, y, w, h, options = {}) {
4062
4676
  options.Subtype = 'Square';
4063
4677
  options.Contents = new String();
4064
4678
  return this.annotate(x, y, w, h, options);
4065
4679
  },
4066
4680
 
4067
- ellipseAnnotation(x, y, w, h, options) {
4068
- if (options == null) {
4069
- options = {};
4070
- }
4681
+ ellipseAnnotation(x, y, w, h, options = {}) {
4071
4682
  options.Subtype = 'Circle';
4072
4683
  options.Contents = new String();
4073
4684
  return this.annotate(x, y, w, h, options);
4074
4685
  },
4075
4686
 
4076
- textAnnotation(x, y, w, h, text, options) {
4077
- if (options == null) {
4078
- options = {};
4079
- }
4687
+ textAnnotation(x, y, w, h, text, options = {}) {
4080
4688
  options.Subtype = 'FreeText';
4081
4689
  options.Contents = new String(text);
4082
4690
  options.DA = new String();
@@ -4086,28 +4694,25 @@ var AnnotationsMixin = {
4086
4694
  _convertRect(x1, y1, w, h) {
4087
4695
  // flip y1 and y2
4088
4696
  let y2 = y1;
4089
- y1 += h;
4697
+ y1 += h; // make x2
4090
4698
 
4091
- // make x2
4092
- let x2 = x1 + w;
4699
+ let x2 = x1 + w; // apply current transformation matrix to points
4093
4700
 
4094
- // apply current transformation matrix to points
4095
4701
  const [m0, m1, m2, m3, m4, m5] = this._ctm;
4096
4702
  x1 = m0 * x1 + m2 * y1 + m4;
4097
4703
  y1 = m1 * x1 + m3 * y1 + m5;
4098
4704
  x2 = m0 * x2 + m2 * y2 + m4;
4099
4705
  y2 = m1 * x2 + m3 * y2 + m5;
4100
-
4101
4706
  return [x1, y1, x2, y2];
4102
4707
  }
4708
+
4103
4709
  };
4104
4710
 
4105
4711
  class PDFOutline {
4106
- constructor(document, parent, title, dest, options) {
4712
+ constructor(document, parent, title, dest, options = {
4713
+ expanded: false
4714
+ }) {
4107
4715
  this.document = document;
4108
- if (options == null) {
4109
- options = { expanded: false };
4110
- }
4111
4716
  this.options = options;
4112
4717
  this.outlineData = {};
4113
4718
 
@@ -4127,13 +4732,11 @@ class PDFOutline {
4127
4732
  this.children = [];
4128
4733
  }
4129
4734
 
4130
- addItem(title, options) {
4131
- if (options == null) {
4132
- options = { expanded: false };
4133
- }
4735
+ addItem(title, options = {
4736
+ expanded: false
4737
+ }) {
4134
4738
  const result = new PDFOutline(this.document, this.dictionary, title, this.document.page, options);
4135
4739
  this.children.push(result);
4136
-
4137
4740
  return result;
4138
4741
  }
4139
4742
 
@@ -4150,18 +4753,22 @@ class PDFOutline {
4150
4753
 
4151
4754
  for (let i = 0, len = this.children.length; i < len; i++) {
4152
4755
  const child = this.children[i];
4756
+
4153
4757
  if (i > 0) {
4154
4758
  child.outlineData.Prev = this.children[i - 1].dictionary;
4155
4759
  }
4760
+
4156
4761
  if (i < this.children.length - 1) {
4157
4762
  child.outlineData.Next = this.children[i + 1].dictionary;
4158
4763
  }
4764
+
4159
4765
  child.endOutline();
4160
4766
  }
4161
4767
  }
4162
4768
 
4163
4769
  return this.dictionary.end();
4164
4770
  }
4771
+
4165
4772
  }
4166
4773
 
4167
4774
  var OutlineMixin = {
@@ -4171,11 +4778,13 @@ var OutlineMixin = {
4171
4778
 
4172
4779
  endOutline() {
4173
4780
  this.outline.endOutline();
4781
+
4174
4782
  if (this.outline.children.length > 0) {
4175
4783
  this._root.data.Outlines = this.outline.dictionary;
4176
4784
  return this._root.data.PageMode = 'UseOutlines';
4177
4785
  }
4178
4786
  }
4787
+
4179
4788
  };
4180
4789
 
4181
4790
  /*
@@ -4186,35 +4795,36 @@ By Devon Govett
4186
4795
  class PDFDocument extends stream.Readable {
4187
4796
  constructor(options = {}) {
4188
4797
  super(options);
4189
- this.options = options;
4798
+ this.options = options; // PDF version
4190
4799
 
4191
- // PDF version
4192
4800
  switch (options.pdfVersion) {
4193
4801
  case '1.4':
4194
4802
  this.version = 1.4;
4195
4803
  break;
4804
+
4196
4805
  case '1.5':
4197
4806
  this.version = 1.5;
4198
4807
  break;
4808
+
4199
4809
  case '1.6':
4200
4810
  this.version = 1.6;
4201
4811
  break;
4812
+
4202
4813
  case '1.7':
4203
4814
  case '1.7ext3':
4204
4815
  this.version = 1.7;
4205
4816
  break;
4817
+
4206
4818
  default:
4207
4819
  this.version = 1.3;
4208
4820
  break;
4209
- }
4821
+ } // Whether streams should be compressed
4210
4822
 
4211
- // Whether streams should be compressed
4212
- this.compress = this.options.compress != null ? this.options.compress : true;
4213
4823
 
4824
+ this.compress = this.options.compress != null ? this.options.compress : true;
4214
4825
  this._pageBuffer = [];
4215
- this._pageBufferStart = 0;
4826
+ this._pageBufferStart = 0; // The PDF object store
4216
4827
 
4217
- // The PDF object store
4218
4828
  this._offsets = [];
4219
4829
  this._waiting = 0;
4220
4830
  this._ended = false;
@@ -4224,36 +4834,24 @@ class PDFDocument extends stream.Readable {
4224
4834
  Count: 0,
4225
4835
  Kids: []
4226
4836
  });
4227
-
4228
- Pages.finalize = function () {
4229
- this.offset = this.document._offset;
4230
- this.document._write(this.id + ' ' + this.gen + ' obj');
4231
- this.document._write('<<');
4232
- this.document._write('/Type /Pages');
4233
- this.document._write(`/Count ${this.data.Count}`);
4234
- this.document._write(`/Kids [${Buffer.concat(this.data.Kids).slice(0, -1).toString()}]`);
4235
- this.document._write('>>');
4236
- this.document._write('endobj');
4237
- return this.document._refEnd(this);
4238
- };
4239
-
4837
+ const Names = this.ref({
4838
+ Dests: new PDFNameTree()
4839
+ });
4240
4840
  this._root = this.ref({
4241
4841
  Type: 'Catalog',
4242
- Pages
4243
- });
4842
+ Pages,
4843
+ Names
4844
+ }); // The current page
4244
4845
 
4245
- // The current page
4246
- this.page = null;
4846
+ this.page = null; // Initialize mixins
4247
4847
 
4248
- // Initialize mixins
4249
4848
  this.initColor();
4250
4849
  this.initVector();
4251
4850
  this.initFonts(options.font);
4252
4851
  this.initText();
4253
4852
  this.initImages();
4254
- this.initOutline();
4853
+ this.initOutline(); // Initialize the metadata
4255
4854
 
4256
- // Initialize the metadata
4257
4855
  this.info = {
4258
4856
  Producer: 'PDFKit',
4259
4857
  Creator: 'PDFKit',
@@ -4265,22 +4863,20 @@ class PDFDocument extends stream.Readable {
4265
4863
  const val = this.options.info[key];
4266
4864
  this.info[key] = val;
4267
4865
  }
4268
- }
4866
+ } // Generate file ID
4269
4867
 
4270
- // Generate file ID
4271
- this._id = PDFSecurity.generateFileID(this.info);
4272
4868
 
4273
- // Initialize security settings
4274
- this._security = PDFSecurity.create(this, options);
4869
+ this._id = PDFSecurity.generateFileID(this.info); // Initialize security settings
4275
4870
 
4276
- // Write the header
4871
+ this._security = PDFSecurity.create(this, options); // Write the header
4277
4872
  // PDF version
4278
- this._write(`%PDF-${this.version}`);
4279
4873
 
4280
- // 4 binary chars, as recommended by the spec
4281
- this._write('%\xFF\xFF\xFF\xFF');
4874
+ this._write(`%PDF-${this.version}`); // 4 binary chars, as recommended by the spec
4875
+
4876
+
4877
+ this._write('%\xFF\xFF\xFF\xFF'); // Add the first page
4878
+
4282
4879
 
4283
- // Add the first page
4284
4880
  if (this.options.autoFirstPage !== false) {
4285
4881
  this.addPage();
4286
4882
  }
@@ -4289,41 +4885,45 @@ class PDFDocument extends stream.Readable {
4289
4885
  addPage(options) {
4290
4886
  // end the current page if needed
4291
4887
  if (options == null) {
4292
- ({ options } = this);
4888
+ ({
4889
+ options
4890
+ } = this);
4293
4891
  }
4892
+
4294
4893
  if (!this.options.bufferPages) {
4295
4894
  this.flushPages();
4296
- }
4895
+ } // create a page object
4896
+
4297
4897
 
4298
- // create a page object
4299
4898
  this.page = new PDFPage(this, options);
4300
- this._pageBuffer.push(this.page);
4301
4899
 
4302
- // add the page to the object store
4900
+ this._pageBuffer.push(this.page); // add the page to the object store
4901
+
4902
+
4303
4903
  const pages = this._root.data.Pages.data;
4304
- pages.Kids.push(new Buffer(this.page.dictionary + ' '));
4305
- pages.Count++;
4904
+ pages.Kids.push(this.page.dictionary);
4905
+ pages.Count++; // reset x and y coordinates
4306
4906
 
4307
- // reset x and y coordinates
4308
4907
  this.x = this.page.margins.left;
4309
- this.y = this.page.margins.top;
4310
-
4311
- // flip PDF coordinate system so that the origin is in
4908
+ this.y = this.page.margins.top; // flip PDF coordinate system so that the origin is in
4312
4909
  // the top left rather than the bottom left
4910
+
4313
4911
  this._ctm = [1, 0, 0, 1, 0, 0];
4314
4912
  this.transform(1, 0, 0, -1, 0, this.page.height);
4315
-
4316
4913
  this.emit('pageAdded');
4317
-
4318
4914
  return this;
4319
4915
  }
4320
4916
 
4321
4917
  bufferedPageRange() {
4322
- return { start: this._pageBufferStart, count: this._pageBuffer.length };
4918
+ return {
4919
+ start: this._pageBufferStart,
4920
+ count: this._pageBuffer.length
4921
+ };
4323
4922
  }
4324
4923
 
4325
4924
  switchToPage(n) {
4326
4925
  let page;
4926
+
4327
4927
  if (!(page = this._pageBuffer[n - this._pageBufferStart])) {
4328
4928
  throw new Error(`switchToPage(${n}) out of bounds, current buffer covers pages ${this._pageBufferStart} to ${this._pageBufferStart + this._pageBuffer.length - 1}`);
4329
4929
  }
@@ -4337,20 +4937,38 @@ class PDFDocument extends stream.Readable {
4337
4937
  const pages = this._pageBuffer;
4338
4938
  this._pageBuffer = [];
4339
4939
  this._pageBufferStart += pages.length;
4940
+
4340
4941
  for (let page of pages) {
4341
4942
  page.end();
4342
4943
  }
4343
4944
  }
4344
4945
 
4946
+ addNamedDestination(name, ...args) {
4947
+ if (args.length === 0) {
4948
+ args = ['XYZ', null, null, null];
4949
+ }
4950
+
4951
+ if (args[0] === 'XYZ' && args[2] !== null) {
4952
+ args[2] = this.page.height - args[2];
4953
+ }
4954
+
4955
+ args.unshift(this.page.dictionary);
4956
+
4957
+ this._root.data.Names.data.Dests.add(name, args);
4958
+ }
4959
+
4345
4960
  ref(data) {
4346
4961
  const ref = new PDFReference(this, this._offsets.length + 1, data);
4962
+
4347
4963
  this._offsets.push(null); // placeholder for this object's offset once it is finalized
4964
+
4965
+
4348
4966
  this._waiting++;
4349
4967
  return ref;
4350
4968
  }
4351
4969
 
4352
- _read() {}
4353
- // do nothing, but this method is required by node
4970
+ _read() {} // do nothing, but this method is required by node
4971
+
4354
4972
 
4355
4973
  _write(data) {
4356
4974
  if (!Buffer.isBuffer(data)) {
@@ -4368,8 +4986,10 @@ class PDFDocument extends stream.Readable {
4368
4986
 
4369
4987
  _refEnd(ref) {
4370
4988
  this._offsets[ref.id - 1] = ref.offset;
4989
+
4371
4990
  if (--this._waiting === 0 && this._ended) {
4372
4991
  this._finalize();
4992
+
4373
4993
  return this._ended = false;
4374
4994
  }
4375
4995
  }
@@ -4380,34 +5000,25 @@ class PDFDocument extends stream.Readable {
4380
5000
  PDFDocument#write is deprecated, and will be removed in a future version of PDFKit. \
4381
5001
  Please pipe the document into a Node stream.\
4382
5002
  `);
4383
-
4384
5003
  console.warn(err.stack);
4385
-
4386
5004
  this.pipe(fs.createWriteStream(filename));
4387
5005
  this.end();
4388
5006
  return this.once('end', fn);
4389
5007
  }
4390
5008
 
4391
- output(fn) {
4392
- // more difficult to support this. It would involve concatenating all the buffers together
4393
- throw new Error(`\
4394
- PDFDocument#output is deprecated, and has been removed from PDFKit. \
4395
- Please pipe the document into a Node stream.\
4396
- `);
4397
- }
4398
-
4399
5009
  end() {
4400
5010
  this.flushPages();
4401
5011
  this._info = this.ref();
5012
+
4402
5013
  for (let key in this.info) {
4403
5014
  let val = this.info[key];
5015
+
4404
5016
  if (typeof val === 'string') {
4405
5017
  val = new String(val);
4406
5018
  }
4407
5019
 
4408
5020
  let entry = this.ref(val);
4409
5021
  entry.end();
4410
-
4411
5022
  this._info.data[key] = entry;
4412
5023
  }
4413
5024
 
@@ -4421,8 +5032,11 @@ Please pipe the document into a Node stream.\
4421
5032
  this.endOutline();
4422
5033
 
4423
5034
  this._root.end();
5035
+
4424
5036
  this._root.data.Pages.end();
4425
5037
 
5038
+ this._root.data.Names.end();
5039
+
4426
5040
  if (this._security) {
4427
5041
  this._security.end();
4428
5042
  }
@@ -4437,40 +5051,49 @@ Please pipe the document into a Node stream.\
4437
5051
  _finalize(fn) {
4438
5052
  // generate xref
4439
5053
  const xRefOffset = this._offset;
5054
+
4440
5055
  this._write('xref');
5056
+
4441
5057
  this._write(`0 ${this._offsets.length + 1}`);
5058
+
4442
5059
  this._write('0000000000 65535 f ');
4443
5060
 
4444
5061
  for (let offset of this._offsets) {
4445
5062
  offset = `0000000000${offset}`.slice(-10);
5063
+
4446
5064
  this._write(offset + ' 00000 n ');
4447
- }
5065
+ } // trailer
5066
+
4448
5067
 
4449
- // trailer
4450
5068
  const trailer = {
4451
5069
  Size: this._offsets.length + 1,
4452
5070
  Root: this._root,
4453
5071
  Info: this._info,
4454
5072
  ID: [this._id, this._id]
4455
5073
  };
5074
+
4456
5075
  if (this._security) {
4457
5076
  trailer.Encrypt = this._security.dictionary;
4458
5077
  }
4459
5078
 
4460
5079
  this._write('trailer');
5080
+
4461
5081
  this._write(PDFObject.convert(trailer));
4462
5082
 
4463
5083
  this._write('startxref');
5084
+
4464
5085
  this._write(`${xRefOffset}`);
4465
- this._write('%%EOF');
4466
5086
 
4467
- // end the stream
5087
+ this._write('%%EOF'); // end the stream
5088
+
5089
+
4468
5090
  return this.push(null);
4469
5091
  }
4470
5092
 
4471
5093
  toString() {
4472
5094
  return '[object PDFDocument]';
4473
5095
  }
5096
+
4474
5097
  }
4475
5098
 
4476
5099
  const mixin = methods => {