pdfkit 0.16.0 → 0.17.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
@@ -10,23 +10,15 @@ var LineBreaker = require('linebreak');
10
10
  var exif = require('jpeg-exif');
11
11
  var PNG = require('png-js');
12
12
 
13
- /*
14
- PDFAbstractReference - abstract class for PDF reference
15
- */
16
-
17
13
  class PDFAbstractReference {
18
14
  toString() {
19
15
  throw new Error('Must be implemented by subclasses');
20
16
  }
21
17
  }
22
18
 
23
- /*
24
- PDFTree - abstract base class for name and number tree objects
25
- */
26
19
  class PDFTree {
27
20
  constructor(options = {}) {
28
21
  this._items = {};
29
- // disable /Limits output for this tree
30
22
  this.limits = typeof options.limits === 'boolean' ? options.limits : true;
31
23
  }
32
24
  add(key, val) {
@@ -36,7 +28,6 @@ class PDFTree {
36
28
  return this._items[key];
37
29
  }
38
30
  toString() {
39
- // Needs to be sorted by key
40
31
  const sortedKeys = Object.keys(this._items).sort((a, b) => this._compareKeys(a, b));
41
32
  const out = ['<<'];
42
33
  if (this.limits && sortedKeys.length > 1) {
@@ -52,15 +43,13 @@ class PDFTree {
52
43
  out.push('>>');
53
44
  return out.join('\n');
54
45
  }
55
- _compareKeys(/*a, b*/
56
- ) {
46
+ _compareKeys() {
57
47
  throw new Error('Must be implemented by subclasses');
58
48
  }
59
49
  _keysName() {
60
50
  throw new Error('Must be implemented by subclasses');
61
51
  }
62
- _dataForKey(/*k*/
63
- ) {
52
+ _dataForKey() {
64
53
  throw new Error('Must be implemented by subclasses');
65
54
  }
66
55
  }
@@ -85,10 +74,6 @@ class SpotColor {
85
74
  }
86
75
  }
87
76
 
88
- /*
89
- PDFObject - converts JavaScript types into their corresponding PDF types.
90
- By Devon Govett
91
- */
92
77
  const pad = (str, length) => (Array(length + 1).join('0') + str).slice(-length);
93
78
  const escapableRe = /[\n\r\t\b\f()\\]/g;
94
79
  const escapable = {
@@ -101,8 +86,6 @@ const escapable = {
101
86
  '(': '\\(',
102
87
  ')': '\\)'
103
88
  };
104
-
105
- // Convert little endian UTF-16 to big endian
106
89
  const swapBytes = function (buff) {
107
90
  const l = buff.length;
108
91
  if (l & 0x01) {
@@ -118,14 +101,10 @@ const swapBytes = function (buff) {
118
101
  };
119
102
  class PDFObject {
120
103
  static convert(object, encryptFn = null) {
121
- // String literals are converted to the PDF name type
122
104
  if (typeof object === 'string') {
123
105
  return `/${object}`;
124
-
125
- // String objects are converted to PDF strings (UTF-16)
126
106
  } else if (object instanceof String) {
127
107
  let string = object;
128
- // Detect if this is a unicode string
129
108
  let isUnicode = false;
130
109
  for (let i = 0, end = string.length; i < end; i++) {
131
110
  if (string.charCodeAt(i) > 0x7f) {
@@ -133,39 +112,27 @@ class PDFObject {
133
112
  break;
134
113
  }
135
114
  }
136
-
137
- // If so, encode it as big endian UTF-16
138
115
  let stringBuffer;
139
116
  if (isUnicode) {
140
117
  stringBuffer = swapBytes(Buffer.from(`\ufeff${string}`, 'utf16le'));
141
118
  } else {
142
119
  stringBuffer = Buffer.from(string.valueOf(), 'ascii');
143
120
  }
144
-
145
- // Encrypt the string when necessary
146
121
  if (encryptFn) {
147
122
  string = encryptFn(stringBuffer).toString('binary');
148
123
  } else {
149
124
  string = stringBuffer.toString('binary');
150
125
  }
151
-
152
- // Escape characters as required by the spec
153
126
  string = string.replace(escapableRe, c => escapable[c]);
154
127
  return `(${string})`;
155
-
156
- // Buffers are converted to PDF hex strings
157
128
  } else if (Buffer.isBuffer(object)) {
158
129
  return `<${object.toString('hex')}>`;
159
130
  } else if (object instanceof PDFAbstractReference || object instanceof PDFTree || object instanceof SpotColor) {
160
131
  return object.toString();
161
132
  } else if (object instanceof Date) {
162
133
  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';
163
-
164
- // Encrypt the string when necessary
165
134
  if (encryptFn) {
166
135
  string = encryptFn(Buffer.from(string, 'ascii')).toString('binary');
167
-
168
- // Escape characters as required by the spec
169
136
  string = string.replace(escapableRe, c => escapable[c]);
170
137
  }
171
138
  return `(${string})`;
@@ -194,10 +161,6 @@ class PDFObject {
194
161
  }
195
162
  }
196
163
 
197
- /*
198
- PDFReference - represents a reference to another object in the PDF object heirarchy
199
- By Devon Govett
200
- */
201
164
  class PDFReference extends PDFAbstractReference {
202
165
  constructor(document, id, data = {}) {
203
166
  super();
@@ -220,14 +183,14 @@ class PDFReference extends PDFAbstractReference {
220
183
  this.buffer.push(chunk);
221
184
  this.data.Length += chunk.length;
222
185
  if (this.compress) {
223
- return this.data.Filter = 'FlateDecode';
186
+ this.data.Filter = 'FlateDecode';
224
187
  }
225
188
  }
226
189
  end(chunk) {
227
190
  if (chunk) {
228
191
  this.write(chunk);
229
192
  }
230
- return this.finalize();
193
+ this.finalize();
231
194
  }
232
195
  finalize() {
233
196
  this.offset = this.document._offset;
@@ -247,7 +210,7 @@ class PDFReference extends PDFAbstractReference {
247
210
  if (this.buffer.length) {
248
211
  this.document._write('stream');
249
212
  this.document._write(this.buffer);
250
- this.buffer = []; // free up memory
213
+ this.buffer = [];
251
214
  this.document._write('\nendstream');
252
215
  }
253
216
  this.document._write('endobj');
@@ -258,10 +221,69 @@ class PDFReference extends PDFAbstractReference {
258
221
  }
259
222
  }
260
223
 
261
- /*
262
- PDFPage - represents a single page in the PDF document
263
- By Devon Govett
264
- */
224
+ function PDFNumber(n) {
225
+ return Math.fround(n);
226
+ }
227
+ function normalizeSides(sides, defaultDefinition = undefined, transformer = v => v) {
228
+ if (sides == null || typeof sides === 'object' && Object.keys(sides).length === 0) {
229
+ sides = defaultDefinition;
230
+ }
231
+ if (sides == null || typeof sides !== 'object') {
232
+ sides = {
233
+ top: sides,
234
+ right: sides,
235
+ bottom: sides,
236
+ left: sides
237
+ };
238
+ } else if (Array.isArray(sides)) {
239
+ if (sides.length === 2) {
240
+ sides = {
241
+ vertical: sides[0],
242
+ horizontal: sides[1]
243
+ };
244
+ } else {
245
+ sides = {
246
+ top: sides[0],
247
+ right: sides[1],
248
+ bottom: sides[2],
249
+ left: sides[3]
250
+ };
251
+ }
252
+ }
253
+ if ('vertical' in sides || 'horizontal' in sides) {
254
+ sides = {
255
+ top: sides.vertical,
256
+ right: sides.horizontal,
257
+ bottom: sides.vertical,
258
+ left: sides.horizontal
259
+ };
260
+ }
261
+ return {
262
+ top: transformer(sides.top),
263
+ right: transformer(sides.right),
264
+ bottom: transformer(sides.bottom),
265
+ left: transformer(sides.left)
266
+ };
267
+ }
268
+ const MM_TO_CM = 1 / 10;
269
+ const CM_TO_IN = 1 / 2.54;
270
+ const PX_TO_IN = 1 / 96;
271
+ const IN_TO_PT = 72;
272
+ const PC_TO_PT = 12;
273
+ function cosine(a) {
274
+ if (a === 0) return 1;
275
+ if (a === 90) return 0;
276
+ if (a === 180) return -1;
277
+ if (a === 270) return 0;
278
+ return Math.cos(a * Math.PI / 180);
279
+ }
280
+ function sine(a) {
281
+ if (a === 0) return 0;
282
+ if (a === 90) return 1;
283
+ if (a === 180) return 0;
284
+ if (a === 270) return -1;
285
+ return Math.sin(a * Math.PI / 180);
286
+ }
265
287
 
266
288
  const DEFAULT_MARGINS = {
267
289
  top: 72,
@@ -324,35 +346,19 @@ const SIZES = {
324
346
  class PDFPage {
325
347
  constructor(document, options = {}) {
326
348
  this.document = document;
349
+ this._options = options;
327
350
  this.size = options.size || 'letter';
328
351
  this.layout = options.layout || 'portrait';
329
-
330
- // process margins
331
- if (typeof options.margin === 'number') {
332
- this.margins = {
333
- top: options.margin,
334
- left: options.margin,
335
- bottom: options.margin,
336
- right: options.margin
337
- };
338
-
339
- // default to 1 inch margins
340
- } else {
341
- this.margins = options.margins || DEFAULT_MARGINS;
342
- }
343
-
344
- // calculate page dimensions
345
352
  const dimensions = Array.isArray(this.size) ? this.size : SIZES[this.size.toUpperCase()];
346
353
  this.width = dimensions[this.layout === 'portrait' ? 0 : 1];
347
354
  this.height = dimensions[this.layout === 'portrait' ? 1 : 0];
348
355
  this.content = this.document.ref();
349
-
350
- // Initialize the Font, XObject, and ExtGState dictionaries
356
+ if (options.font) document.font(options.font, options.fontFamily);
357
+ if (options.fontSize) document.fontSize(options.fontSize);
358
+ this.margins = normalizeSides(options.margin ?? options.margins, DEFAULT_MARGINS, x => document.sizeToPoint(x, 0, this));
351
359
  this.resources = this.document.ref({
352
360
  ProcSet: ['PDF', 'Text', 'ImageB', 'ImageC', 'ImageI']
353
361
  });
354
-
355
- // The page dictionary
356
362
  this.dictionary = this.document.ref({
357
363
  Type: 'Page',
358
364
  Parent: this.document._root.data.Pages,
@@ -362,8 +368,6 @@ class PDFPage {
362
368
  });
363
369
  this.markings = [];
364
370
  }
365
-
366
- // Lazily create these objects
367
371
  get fonts() {
368
372
  const data = this.resources.data;
369
373
  return data.Font != null ? data.Font : data.Font = {};
@@ -392,14 +396,18 @@ class PDFPage {
392
396
  const data = this.dictionary.data;
393
397
  return data.StructParents != null ? data.StructParents : data.StructParents = this.document.createStructParentTreeNextKey();
394
398
  }
399
+ get contentWidth() {
400
+ return this.width - this.margins.left - this.margins.right;
401
+ }
402
+ get contentHeight() {
403
+ return this.height - this.margins.top - this.margins.bottom;
404
+ }
395
405
  maxY() {
396
406
  return this.height - this.margins.bottom;
397
407
  }
398
408
  write(chunk) {
399
409
  return this.content.write(chunk);
400
410
  }
401
-
402
- // Set tab order if document is tagged for accessibility.
403
411
  _setTabOrder() {
404
412
  if (!this.dictionary.Tabs && this.document.hasMarkInfoDictionary()) {
405
413
  this.dictionary.data.Tabs = 'S';
@@ -417,191 +425,57 @@ class PDFPage {
417
425
  }
418
426
  }
419
427
 
420
- /*
421
- PDFNameTree - represents a name tree object
422
- */
423
428
  class PDFNameTree extends PDFTree {
424
429
  _compareKeys(a, b) {
425
430
  return a.localeCompare(b);
426
431
  }
427
432
  _keysName() {
428
- return "Names";
433
+ return 'Names';
429
434
  }
430
435
  _dataForKey(k) {
431
436
  return new String(k);
432
437
  }
433
438
  }
434
439
 
435
- /**
436
- * Check if value is in a range group.
437
- * @param {number} value
438
- * @param {number[]} rangeGroup
439
- * @returns {boolean}
440
- */
441
440
  function inRange(value, rangeGroup) {
442
441
  if (value < rangeGroup[0]) return false;
443
442
  let startRange = 0;
444
443
  let endRange = rangeGroup.length / 2;
445
444
  while (startRange <= endRange) {
446
445
  const middleRange = Math.floor((startRange + endRange) / 2);
447
-
448
- // actual array index
449
446
  const arrayIndex = middleRange * 2;
450
-
451
- // Check if value is in range pointed by actual index
452
447
  if (value >= rangeGroup[arrayIndex] && value <= rangeGroup[arrayIndex + 1]) {
453
448
  return true;
454
449
  }
455
450
  if (value > rangeGroup[arrayIndex + 1]) {
456
- // Search Right Side Of Array
457
451
  startRange = middleRange + 1;
458
452
  } else {
459
- // Search Left Side Of Array
460
453
  endRange = middleRange - 1;
461
454
  }
462
455
  }
463
456
  return false;
464
457
  }
465
458
 
466
- // prettier-ignore-start
467
- /**
468
- * A.1 Unassigned code points in Unicode 3.2
469
- * @link https://tools.ietf.org/html/rfc3454#appendix-A.1
470
- */
471
459
  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];
472
- // prettier-ignore-end
473
-
474
460
  const isUnassignedCodePoint = character => inRange(character, unassigned_code_points);
475
-
476
- // prettier-ignore-start
477
- /**
478
- * B.1 Commonly mapped to nothing
479
- * @link https://tools.ietf.org/html/rfc3454#appendix-B.1
480
- */
481
461
  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];
482
- // prettier-ignore-end
483
-
484
462
  const isCommonlyMappedToNothing = character => inRange(character, commonly_mapped_to_nothing);
485
-
486
- // prettier-ignore-start
487
- /**
488
- * C.1.2 Non-ASCII space characters
489
- * @link https://tools.ietf.org/html/rfc3454#appendix-C.1.2
490
- */
491
- const non_ASCII_space_characters = [0x00a0, 0x00a0 /* NO-BREAK SPACE */, 0x1680, 0x1680 /* OGHAM SPACE MARK */, 0x2000, 0x2000 /* EN QUAD */, 0x2001, 0x2001 /* EM QUAD */, 0x2002, 0x2002 /* EN SPACE */, 0x2003, 0x2003 /* EM SPACE */, 0x2004, 0x2004 /* THREE-PER-EM SPACE */, 0x2005, 0x2005 /* FOUR-PER-EM SPACE */, 0x2006, 0x2006 /* SIX-PER-EM SPACE */, 0x2007, 0x2007 /* FIGURE SPACE */, 0x2008, 0x2008 /* PUNCTUATION SPACE */, 0x2009, 0x2009 /* THIN SPACE */, 0x200a, 0x200a /* HAIR SPACE */, 0x200b, 0x200b /* ZERO WIDTH SPACE */, 0x202f, 0x202f /* NARROW NO-BREAK SPACE */, 0x205f, 0x205f /* MEDIUM MATHEMATICAL SPACE */, 0x3000, 0x3000 /* IDEOGRAPHIC SPACE */];
492
- // prettier-ignore-end
493
-
463
+ const non_ASCII_space_characters = [0x00a0, 0x00a0, 0x1680, 0x1680, 0x2000, 0x2000, 0x2001, 0x2001, 0x2002, 0x2002, 0x2003, 0x2003, 0x2004, 0x2004, 0x2005, 0x2005, 0x2006, 0x2006, 0x2007, 0x2007, 0x2008, 0x2008, 0x2009, 0x2009, 0x200a, 0x200a, 0x200b, 0x200b, 0x202f, 0x202f, 0x205f, 0x205f, 0x3000, 0x3000];
494
464
  const isNonASCIISpaceCharacter = character => inRange(character, non_ASCII_space_characters);
495
-
496
- // prettier-ignore-start
497
- const non_ASCII_controls_characters = [
498
- /**
499
- * C.2.2 Non-ASCII control characters
500
- * @link https://tools.ietf.org/html/rfc3454#appendix-C.2.2
501
- */
502
- 0x0080, 0x009f /* [CONTROL CHARACTERS] */, 0x06dd, 0x06dd /* ARABIC END OF AYAH */, 0x070f, 0x070f /* SYRIAC ABBREVIATION MARK */, 0x180e, 0x180e /* MONGOLIAN VOWEL SEPARATOR */, 0x200c, 0x200c /* ZERO WIDTH NON-JOINER */, 0x200d, 0x200d /* ZERO WIDTH JOINER */, 0x2028, 0x2028 /* LINE SEPARATOR */, 0x2029, 0x2029 /* PARAGRAPH SEPARATOR */, 0x2060, 0x2060 /* WORD JOINER */, 0x2061, 0x2061 /* FUNCTION APPLICATION */, 0x2062, 0x2062 /* INVISIBLE TIMES */, 0x2063, 0x2063 /* INVISIBLE SEPARATOR */, 0x206a, 0x206f /* [CONTROL CHARACTERS] */, 0xfeff, 0xfeff /* ZERO WIDTH NO-BREAK SPACE */, 0xfff9, 0xfffc /* [CONTROL CHARACTERS] */, 0x1d173, 0x1d17a /* [MUSICAL CONTROL CHARACTERS] */];
503
- const non_character_codepoints = [
504
- /**
505
- * C.4 Non-character code points
506
- * @link https://tools.ietf.org/html/rfc3454#appendix-C.4
507
- */
508
- 0xfdd0, 0xfdef /* [NONCHARACTER CODE POINTS] */, 0xfffe, 0xffff /* [NONCHARACTER CODE POINTS] */, 0x1fffe, 0x1ffff /* [NONCHARACTER CODE POINTS] */, 0x2fffe, 0x2ffff /* [NONCHARACTER CODE POINTS] */, 0x3fffe, 0x3ffff /* [NONCHARACTER CODE POINTS] */, 0x4fffe, 0x4ffff /* [NONCHARACTER CODE POINTS] */, 0x5fffe, 0x5ffff /* [NONCHARACTER CODE POINTS] */, 0x6fffe, 0x6ffff /* [NONCHARACTER CODE POINTS] */, 0x7fffe, 0x7ffff /* [NONCHARACTER CODE POINTS] */, 0x8fffe, 0x8ffff /* [NONCHARACTER CODE POINTS] */, 0x9fffe, 0x9ffff /* [NONCHARACTER CODE POINTS] */, 0xafffe, 0xaffff /* [NONCHARACTER CODE POINTS] */, 0xbfffe, 0xbffff /* [NONCHARACTER CODE POINTS] */, 0xcfffe, 0xcffff /* [NONCHARACTER CODE POINTS] */, 0xdfffe, 0xdffff /* [NONCHARACTER CODE POINTS] */, 0xefffe, 0xeffff /* [NONCHARACTER CODE POINTS] */, 0x10fffe, 0x10ffff /* [NONCHARACTER CODE POINTS] */];
509
-
510
- /**
511
- * 2.3. Prohibited Output
512
- */
513
- const prohibited_characters = [
514
- /**
515
- * C.2.1 ASCII control characters
516
- * @link https://tools.ietf.org/html/rfc3454#appendix-C.2.1
517
- */
518
- 0, 0x001f /* [CONTROL CHARACTERS] */, 0x007f, 0x007f /* DELETE */,
519
- /**
520
- * C.8 Change display properties or are deprecated
521
- * @link https://tools.ietf.org/html/rfc3454#appendix-C.8
522
- */
523
- 0x0340, 0x0340 /* COMBINING GRAVE TONE MARK */, 0x0341, 0x0341 /* COMBINING ACUTE TONE MARK */, 0x200e, 0x200e /* LEFT-TO-RIGHT MARK */, 0x200f, 0x200f /* RIGHT-TO-LEFT MARK */, 0x202a, 0x202a /* LEFT-TO-RIGHT EMBEDDING */, 0x202b, 0x202b /* RIGHT-TO-LEFT EMBEDDING */, 0x202c, 0x202c /* POP DIRECTIONAL FORMATTING */, 0x202d, 0x202d /* LEFT-TO-RIGHT OVERRIDE */, 0x202e, 0x202e /* RIGHT-TO-LEFT OVERRIDE */, 0x206a, 0x206a /* INHIBIT SYMMETRIC SWAPPING */, 0x206b, 0x206b /* ACTIVATE SYMMETRIC SWAPPING */, 0x206c, 0x206c /* INHIBIT ARABIC FORM SHAPING */, 0x206d, 0x206d /* ACTIVATE ARABIC FORM SHAPING */, 0x206e, 0x206e /* NATIONAL DIGIT SHAPES */, 0x206f, 0x206f /* NOMINAL DIGIT SHAPES */,
524
- /**
525
- * C.7 Inappropriate for canonical representation
526
- * @link https://tools.ietf.org/html/rfc3454#appendix-C.7
527
- */
528
- 0x2ff0, 0x2ffb /* [IDEOGRAPHIC DESCRIPTION CHARACTERS] */,
529
- /**
530
- * C.5 Surrogate codes
531
- * @link https://tools.ietf.org/html/rfc3454#appendix-C.5
532
- */
533
- 0xd800, 0xdfff,
534
- /**
535
- * C.3 Private use
536
- * @link https://tools.ietf.org/html/rfc3454#appendix-C.3
537
- */
538
- 0xe000, 0xf8ff /* [PRIVATE USE, PLANE 0] */,
539
- /**
540
- * C.6 Inappropriate for plain text
541
- * @link https://tools.ietf.org/html/rfc3454#appendix-C.6
542
- */
543
- 0xfff9, 0xfff9 /* INTERLINEAR ANNOTATION ANCHOR */, 0xfffa, 0xfffa /* INTERLINEAR ANNOTATION SEPARATOR */, 0xfffb, 0xfffb /* INTERLINEAR ANNOTATION TERMINATOR */, 0xfffc, 0xfffc /* OBJECT REPLACEMENT CHARACTER */, 0xfffd, 0xfffd /* REPLACEMENT CHARACTER */,
544
- /**
545
- * C.9 Tagging characters
546
- * @link https://tools.ietf.org/html/rfc3454#appendix-C.9
547
- */
548
- 0xe0001, 0xe0001 /* LANGUAGE TAG */, 0xe0020, 0xe007f /* [TAGGING CHARACTERS] */,
549
- /**
550
- * C.3 Private use
551
- * @link https://tools.ietf.org/html/rfc3454#appendix-C.3
552
- */
553
-
554
- 0xf0000, 0xffffd /* [PRIVATE USE, PLANE 15] */, 0x100000, 0x10fffd /* [PRIVATE USE, PLANE 16] */];
555
- // prettier-ignore-end
556
-
465
+ const non_ASCII_controls_characters = [0x0080, 0x009f, 0x06dd, 0x06dd, 0x070f, 0x070f, 0x180e, 0x180e, 0x200c, 0x200c, 0x200d, 0x200d, 0x2028, 0x2028, 0x2029, 0x2029, 0x2060, 0x2060, 0x2061, 0x2061, 0x2062, 0x2062, 0x2063, 0x2063, 0x206a, 0x206f, 0xfeff, 0xfeff, 0xfff9, 0xfffc, 0x1d173, 0x1d17a];
466
+ const non_character_codepoints = [0xfdd0, 0xfdef, 0xfffe, 0xffff, 0x1fffe, 0x1ffff, 0x2fffe, 0x2ffff, 0x3fffe, 0x3ffff, 0x4fffe, 0x4ffff, 0x5fffe, 0x5ffff, 0x6fffe, 0x6ffff, 0x7fffe, 0x7ffff, 0x8fffe, 0x8ffff, 0x9fffe, 0x9ffff, 0xafffe, 0xaffff, 0xbfffe, 0xbffff, 0xcfffe, 0xcffff, 0xdfffe, 0xdffff, 0xefffe, 0xeffff, 0x10fffe, 0x10ffff];
467
+ const prohibited_characters = [0, 0x001f, 0x007f, 0x007f, 0x0340, 0x0340, 0x0341, 0x0341, 0x200e, 0x200e, 0x200f, 0x200f, 0x202a, 0x202a, 0x202b, 0x202b, 0x202c, 0x202c, 0x202d, 0x202d, 0x202e, 0x202e, 0x206a, 0x206a, 0x206b, 0x206b, 0x206c, 0x206c, 0x206d, 0x206d, 0x206e, 0x206e, 0x206f, 0x206f, 0x2ff0, 0x2ffb, 0xd800, 0xdfff, 0xe000, 0xf8ff, 0xfff9, 0xfff9, 0xfffa, 0xfffa, 0xfffb, 0xfffb, 0xfffc, 0xfffc, 0xfffd, 0xfffd, 0xe0001, 0xe0001, 0xe0020, 0xe007f, 0xf0000, 0xffffd, 0x100000, 0x10fffd];
557
468
  const isProhibitedCharacter = character => inRange(character, non_ASCII_space_characters) || inRange(character, prohibited_characters) || inRange(character, non_ASCII_controls_characters) || inRange(character, non_character_codepoints);
558
-
559
- // prettier-ignore-start
560
- /**
561
- * D.1 Characters with bidirectional property "R" or "AL"
562
- * @link https://tools.ietf.org/html/rfc3454#appendix-D.1
563
- */
564
469
  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];
565
- // prettier-ignore-end
566
-
567
470
  const isBidirectionalRAL = character => inRange(character, bidirectional_r_al);
568
-
569
- // prettier-ignore-start
570
- /**
571
- * D.2 Characters with bidirectional property "L"
572
- * @link https://tools.ietf.org/html/rfc3454#appendix-D.2
573
- */
574
471
  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];
575
- // prettier-ignore-end
576
-
577
472
  const isBidirectionalL = character => inRange(character, bidirectional_l);
578
473
 
579
- // 2.1. Mapping
580
-
581
- /**
582
- * non-ASCII space characters [StringPrep, C.1.2] that can be
583
- * mapped to SPACE (U+0020)
584
- */
585
474
  const mapping2space = isNonASCIISpaceCharacter;
586
-
587
- /**
588
- * the "commonly mapped to nothing" characters [StringPrep, B.1]
589
- * that can be mapped to nothing.
590
- */
591
475
  const mapping2nothing = isCommonlyMappedToNothing;
592
-
593
- // utils
594
476
  const getCodePoint = character => character.codePointAt(0);
595
477
  const first = x => x[0];
596
478
  const last = x => x[x.length - 1];
597
-
598
- /**
599
- * Convert provided string into an array of Unicode Code Points.
600
- * Based on https://stackoverflow.com/a/21409165/1556249
601
- * and https://www.npmjs.com/package/code-point-at.
602
- * @param {string} input
603
- * @returns {number[]}
604
- */
605
479
  function toCodePoints(input) {
606
480
  const codepoints = [];
607
481
  const size = input.length;
@@ -619,14 +493,6 @@ function toCodePoints(input) {
619
493
  }
620
494
  return codepoints;
621
495
  }
622
-
623
- /**
624
- * SASLprep.
625
- * @param {string} input
626
- * @param {Object} opts
627
- * @param {boolean} opts.allowUnassigned
628
- * @returns {string}
629
- */
630
496
  function saslprep(input, opts = {}) {
631
497
  if (typeof input !== 'string') {
632
498
  throw new TypeError('Expected string.');
@@ -634,49 +500,24 @@ function saslprep(input, opts = {}) {
634
500
  if (input.length === 0) {
635
501
  return '';
636
502
  }
637
-
638
- // 1. Map
639
- const mapped_input = toCodePoints(input)
640
- // 1.1 mapping to space
641
- .map(character => mapping2space(character) ? 0x20 : character)
642
- // 1.2 mapping to nothing
643
- .filter(character => !mapping2nothing(character));
644
-
645
- // 2. Normalize
503
+ const mapped_input = toCodePoints(input).map(character => mapping2space(character) ? 0x20 : character).filter(character => !mapping2nothing(character));
646
504
  const normalized_input = String.fromCodePoint.apply(null, mapped_input).normalize('NFKC');
647
505
  const normalized_map = toCodePoints(normalized_input);
648
-
649
- // 3. Prohibit
650
506
  const hasProhibited = normalized_map.some(isProhibitedCharacter);
651
507
  if (hasProhibited) {
652
508
  throw new Error('Prohibited character, see https://tools.ietf.org/html/rfc4013#section-2.3');
653
509
  }
654
-
655
- // Unassigned Code Points
656
510
  if (opts.allowUnassigned !== true) {
657
511
  const hasUnassigned = normalized_map.some(isUnassignedCodePoint);
658
512
  if (hasUnassigned) {
659
513
  throw new Error('Unassigned code point, see https://tools.ietf.org/html/rfc4013#section-2.5');
660
514
  }
661
515
  }
662
-
663
- // 4. check bidi
664
-
665
516
  const hasBidiRAL = normalized_map.some(isBidirectionalRAL);
666
517
  const hasBidiL = normalized_map.some(isBidirectionalL);
667
-
668
- // 4.1 If a string contains any RandALCat character, the string MUST NOT
669
- // contain any LCat character.
670
518
  if (hasBidiRAL && hasBidiL) {
671
519
  throw new Error('String must not contain RandALCat and LCat at the same time,' + ' see https://tools.ietf.org/html/rfc3454#section-6');
672
520
  }
673
-
674
- /**
675
- * 4.2 If a string contains any RandALCat character, a RandALCat
676
- * character MUST be the first character of the string, and a
677
- * RandALCat character MUST be the last character of the string.
678
- */
679
-
680
521
  const isFirstBidiRAL = isBidirectionalRAL(getCodePoint(first(normalized_input)));
681
522
  const isLastBidiRAL = isBidirectionalRAL(getCodePoint(last(normalized_input)));
682
523
  if (hasBidiRAL && !(isFirstBidiRAL && isLastBidiRAL)) {
@@ -685,15 +526,10 @@ function saslprep(input, opts = {}) {
685
526
  return normalized_input;
686
527
  }
687
528
 
688
- /*
689
- PDFSecurity - represents PDF security settings
690
- By Yang Liu <hi@zesik.com>
691
- */
692
529
  class PDFSecurity {
693
530
  static generateFileID(info = {}) {
694
531
  let infoStr = `${info.CreationDate.getTime()}\n`;
695
532
  for (let key in info) {
696
- // eslint-disable-next-line no-prototype-builtins
697
533
  if (!info.hasOwnProperty(key)) {
698
534
  continue;
699
535
  }
@@ -1067,8 +903,6 @@ class PDFGradient$1 {
1067
903
  }
1068
904
  this.embedded = true;
1069
905
  this.matrix = m;
1070
-
1071
- // if the last stop comes before 100%, add a copy at 100%
1072
906
  const last = this.stops[stopsLength - 1];
1073
907
  if (last[0] < 1) {
1074
908
  this.stops.push([1, last[1], last[2]]);
@@ -1091,14 +925,11 @@ class PDFGradient$1 {
1091
925
  stops.push(fn);
1092
926
  fn.end();
1093
927
  }
1094
-
1095
- // if there are only two stops, we don't need a stitching function
1096
928
  if (stopsLength === 1) {
1097
929
  fn = stops[0];
1098
930
  } else {
1099
931
  fn = this.doc.ref({
1100
932
  FunctionType: 3,
1101
- // stitching function
1102
933
  Domain: [0, 1],
1103
934
  Functions: stops,
1104
935
  Bounds: bounds,
@@ -1179,7 +1010,6 @@ class PDFGradient$1 {
1179
1010
  return pattern;
1180
1011
  }
1181
1012
  apply(stroke) {
1182
- // apply gradient transform to existing document ctm
1183
1013
  const [m0, m1, m2, m3, m4, m5] = this.doc._ctm;
1184
1014
  const [m11, m12, m21, m22, dx, dy] = this.transform;
1185
1015
  const m = [m0 * m11 + m2 * m12, m1 * m11 + m3 * m12, m0 * m21 + m2 * m22, m1 * m21 + m3 * m22, m0 * dx + m2 * dy + m4, m1 * dx + m3 * dy + m5];
@@ -1242,10 +1072,6 @@ var Gradient = {
1242
1072
  PDFRadialGradient: PDFRadialGradient$1
1243
1073
  };
1244
1074
 
1245
- /*
1246
- PDF tiling pattern support. Uncolored only.
1247
- */
1248
-
1249
1075
  const underlyingColorSpaces = ['DeviceCMYK', 'DeviceRGB'];
1250
1076
  class PDFTilingPattern$1 {
1251
1077
  constructor(doc, bBox, xStep, yStep, stream) {
@@ -1256,23 +1082,16 @@ class PDFTilingPattern$1 {
1256
1082
  this.stream = stream;
1257
1083
  }
1258
1084
  createPattern() {
1259
- // no resources needed for our current usage
1260
- // required entry
1261
1085
  const resources = this.doc.ref();
1262
1086
  resources.end();
1263
- // apply default transform matrix (flipped in the default doc._ctm)
1264
- // see document.js & gradient.js
1265
1087
  const [m0, m1, m2, m3, m4, m5] = this.doc._ctm;
1266
1088
  const [m11, m12, m21, m22, dx, dy] = [1, 0, 0, 1, 0, 0];
1267
1089
  const m = [m0 * m11 + m2 * m12, m1 * m11 + m3 * m12, m0 * m21 + m2 * m22, m1 * m21 + m3 * m22, m0 * dx + m2 * dy + m4, m1 * dx + m3 * dy + m5];
1268
1090
  const pattern = this.doc.ref({
1269
1091
  Type: 'Pattern',
1270
1092
  PatternType: 1,
1271
- // tiling
1272
1093
  PaintType: 2,
1273
- // 1-colored, 2-uncolored
1274
1094
  TilingType: 2,
1275
- // 2-no distortion
1276
1095
  BBox: this.bBox,
1277
1096
  XStep: this.xStep,
1278
1097
  YStep: this.yStep,
@@ -1283,8 +1102,6 @@ class PDFTilingPattern$1 {
1283
1102
  return pattern;
1284
1103
  }
1285
1104
  embedPatternColorSpaces() {
1286
- // map each pattern to an underlying color space
1287
- // and embed on each page
1288
1105
  underlyingColorSpaces.forEach(csName => {
1289
1106
  const csId = this.getPatternColorSpaceId(csName);
1290
1107
  if (this.doc.page.colorSpaces[csId]) return;
@@ -1302,24 +1119,17 @@ class PDFTilingPattern$1 {
1302
1119
  this.id = 'P' + this.doc._patternCount;
1303
1120
  this.pattern = this.createPattern();
1304
1121
  }
1305
-
1306
- // patterns are embedded in each page
1307
1122
  if (!this.doc.page.patterns[this.id]) {
1308
1123
  this.doc.page.patterns[this.id] = this.pattern;
1309
1124
  }
1310
1125
  }
1311
1126
  apply(stroke, patternColor) {
1312
- // do any embedding/creating that might be needed
1313
1127
  this.embedPatternColorSpaces();
1314
1128
  this.embed();
1315
1129
  const normalizedColor = this.doc._normalizeColor(patternColor);
1316
1130
  if (!normalizedColor) throw Error(`invalid pattern color. (value: ${patternColor})`);
1317
-
1318
- // select one of the pattern color spaces
1319
1131
  const csId = this.getPatternColorSpaceId(this.doc._getColorSpace(normalizedColor));
1320
1132
  this.doc._setColorSpace(csId, stroke);
1321
-
1322
- // stroke/fill using the pattern and color (in the above underlying color space)
1323
1133
  const op = stroke ? 'SCN' : 'scn';
1324
1134
  return this.doc.addContent(`${normalizedColor.join(' ')} /${this.id} ${op}`);
1325
1135
  }
@@ -1339,11 +1149,10 @@ const {
1339
1149
  var ColorMixin = {
1340
1150
  initColor() {
1341
1151
  this.spotColors = {};
1342
- // The opacity dictionaries
1343
1152
  this._opacityRegistry = {};
1344
1153
  this._opacityCount = 0;
1345
1154
  this._patternCount = 0;
1346
- return this._gradCount = 0;
1155
+ this._gradCount = 0;
1347
1156
  },
1348
1157
  _normalizeColor(color) {
1349
1158
  if (typeof color === 'string') {
@@ -1360,10 +1169,8 @@ var ColorMixin = {
1360
1169
  }
1361
1170
  }
1362
1171
  if (Array.isArray(color)) {
1363
- // RGB
1364
1172
  if (color.length === 3) {
1365
1173
  color = color.map(part => part / 255);
1366
- // CMYK
1367
1174
  } else if (color.length === 4) {
1368
1175
  color = color.map(part => part / 100);
1369
1176
  }
@@ -1375,12 +1182,10 @@ var ColorMixin = {
1375
1182
  if (color instanceof PDFGradient) {
1376
1183
  color.apply(stroke);
1377
1184
  return true;
1378
- // see if tiling pattern, decode & apply it it
1379
1185
  } else if (Array.isArray(color) && color[0] instanceof PDFTilingPattern) {
1380
1186
  color[0].apply(stroke, color[1]);
1381
1187
  return true;
1382
1188
  }
1383
- // any other case should be a normal color and not a pattern
1384
1189
  return this._setColorCore(color, stroke);
1385
1190
  },
1386
1191
  _setColorCore(color, stroke) {
@@ -1414,9 +1219,6 @@ var ColorMixin = {
1414
1219
  if (set) {
1415
1220
  this.fillOpacity(opacity);
1416
1221
  }
1417
-
1418
- // save this for text wrapper, which needs to reset
1419
- // the fill color on new pages
1420
1222
  this._fillColor = [color, opacity];
1421
1223
  return this;
1422
1224
  },
@@ -1672,7 +1474,6 @@ const parse = function (path) {
1672
1474
  if (parameters[c] != null) {
1673
1475
  params = parameters[c];
1674
1476
  if (cmd) {
1675
- // save existing command
1676
1477
  if (curArg.length > 0) {
1677
1478
  args[args.length] = +curArg;
1678
1479
  }
@@ -1690,14 +1491,11 @@ const parse = function (path) {
1690
1491
  continue;
1691
1492
  }
1692
1493
  if (args.length === params) {
1693
- // handle reused commands
1694
1494
  ret[ret.length] = {
1695
1495
  cmd,
1696
1496
  args
1697
1497
  };
1698
1498
  args = [+curArg];
1699
-
1700
- // handle assumed commands
1701
1499
  if (cmd === 'M') {
1702
1500
  cmd = 'L';
1703
1501
  }
@@ -1708,8 +1506,6 @@ const parse = function (path) {
1708
1506
  args[args.length] = +curArg;
1709
1507
  }
1710
1508
  foundDecimal = c === '.';
1711
-
1712
- // fix for negative numbers or repeated decimals with no delimeter between commands
1713
1509
  curArg = ['-', '.'].includes(c) ? c : '';
1714
1510
  } else {
1715
1511
  curArg += c;
@@ -1718,18 +1514,13 @@ const parse = function (path) {
1718
1514
  }
1719
1515
  }
1720
1516
  }
1721
-
1722
- // add the last command
1723
1517
  if (curArg.length > 0) {
1724
1518
  if (args.length === params) {
1725
- // handle reused commands
1726
1519
  ret[ret.length] = {
1727
1520
  cmd,
1728
1521
  args
1729
1522
  };
1730
1523
  args = [+curArg];
1731
-
1732
- // handle assumed commands
1733
1524
  if (cmd === 'M') {
1734
1525
  cmd = 'L';
1735
1526
  }
@@ -1747,10 +1538,7 @@ const parse = function (path) {
1747
1538
  return ret;
1748
1539
  };
1749
1540
  const apply = function (commands, doc) {
1750
- // current point, control point, and subpath starting point
1751
1541
  cx = cy = px = py = sx = sy = 0;
1752
-
1753
- // run the commands
1754
1542
  for (let i = 0; i < commands.length; i++) {
1755
1543
  const c = commands[i];
1756
1544
  if (typeof runners[c.cmd] === 'function') {
@@ -1914,8 +1702,6 @@ const solveArc = function (doc, x, y, coords) {
1914
1702
  doc.bezierCurveTo(...bez);
1915
1703
  }
1916
1704
  };
1917
-
1918
- // from Inkscape svgtopdf, thanks!
1919
1705
  const arcToSegments = function (x, y, rx, ry, large, sweep, rotateX, ox, oy) {
1920
1706
  const th = rotateX * (Math.PI / 180);
1921
1707
  const sin_th = Math.sin(th);
@@ -1991,18 +1777,14 @@ class SVGPath {
1991
1777
  const {
1992
1778
  number: number$1
1993
1779
  } = PDFObject;
1994
-
1995
- // This constant is used to approximate a symmetrical arc using a cubic
1996
- // Bezier curve.
1997
1780
  const KAPPA = 4.0 * ((Math.sqrt(2) - 1.0) / 3.0);
1998
1781
  var VectorMixin = {
1999
1782
  initVector() {
2000
- this._ctm = [1, 0, 0, 1, 0, 0]; // current transformation matrix
2001
- return this._ctmStack = [];
1783
+ this._ctm = [1, 0, 0, 1, 0, 0];
1784
+ this._ctmStack = [];
2002
1785
  },
2003
1786
  save() {
2004
1787
  this._ctmStack.push(this._ctm.slice());
2005
- // TODO: save/restore colorspace and styles so not setting it unnessesarily all the time?
2006
1788
  return this.addContent('q');
2007
1789
  },
2008
1790
  restore() {
@@ -2075,8 +1857,6 @@ var VectorMixin = {
2075
1857
  r = 0;
2076
1858
  }
2077
1859
  r = Math.min(r, 0.5 * w, 0.5 * h);
2078
-
2079
- // amount to inset control points from corners (see `ellipse`)
2080
1860
  const c = r * (1.0 - KAPPA);
2081
1861
  this.moveTo(x + r, y);
2082
1862
  this.lineTo(x + w - r, y);
@@ -2090,7 +1870,6 @@ var VectorMixin = {
2090
1870
  return this.closePath();
2091
1871
  },
2092
1872
  ellipse(x, y, r1, r2) {
2093
- // based on http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas/2173084#2173084
2094
1873
  if (r2 == null) {
2095
1874
  r2 = r1;
2096
1875
  }
@@ -2120,10 +1899,8 @@ var VectorMixin = {
2120
1899
  const HALF_PI = 0.5 * Math.PI;
2121
1900
  let deltaAng = endAngle - startAngle;
2122
1901
  if (Math.abs(deltaAng) > TWO_PI) {
2123
- // draw only full circle if more than that is specified
2124
1902
  deltaAng = TWO_PI;
2125
1903
  } else if (deltaAng !== 0 && anticlockwise !== deltaAng < 0) {
2126
- // necessary to flip direction of rendering
2127
1904
  const dir = anticlockwise ? -1 : 1;
2128
1905
  deltaAng = dir * TWO_PI + deltaAng;
2129
1906
  }
@@ -2131,38 +1908,21 @@ var VectorMixin = {
2131
1908
  const segAng = deltaAng / numSegs;
2132
1909
  const handleLen = segAng / HALF_PI * KAPPA * radius;
2133
1910
  let curAng = startAngle;
2134
-
2135
- // component distances between anchor point and control point
2136
1911
  let deltaCx = -Math.sin(curAng) * handleLen;
2137
1912
  let deltaCy = Math.cos(curAng) * handleLen;
2138
-
2139
- // anchor point
2140
1913
  let ax = x + Math.cos(curAng) * radius;
2141
1914
  let ay = y + Math.sin(curAng) * radius;
2142
-
2143
- // calculate and render segments
2144
1915
  this.moveTo(ax, ay);
2145
1916
  for (let segIdx = 0; segIdx < numSegs; segIdx++) {
2146
- // starting control point
2147
1917
  const cp1x = ax + deltaCx;
2148
1918
  const cp1y = ay + deltaCy;
2149
-
2150
- // step angle
2151
1919
  curAng += segAng;
2152
-
2153
- // next anchor point
2154
1920
  ax = x + Math.cos(curAng) * radius;
2155
1921
  ay = y + Math.sin(curAng) * radius;
2156
-
2157
- // next control point delta
2158
1922
  deltaCx = -Math.sin(curAng) * handleLen;
2159
1923
  deltaCy = Math.cos(curAng) * handleLen;
2160
-
2161
- // ending control point
2162
1924
  const cp2x = ax - deltaCx;
2163
1925
  const cp2y = ay - deltaCy;
2164
-
2165
- // render segment
2166
1926
  this.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, ax, ay);
2167
1927
  }
2168
1928
  return this;
@@ -2223,9 +1983,7 @@ var VectorMixin = {
2223
1983
  return this.addContent(`W${this._windingRule(rule)} n`);
2224
1984
  },
2225
1985
  transform(m11, m12, m21, m22, dx, dy) {
2226
- // keep track of the current transformation matrix
2227
1986
  if (m11 === 1 && m12 === 0 && m21 === 0 && m22 === 1 && dx === 0 && dy === 0) {
2228
- // Ignore identity transforms
2229
1987
  return this;
2230
1988
  }
2231
1989
  const m = this._ctm;
@@ -2389,7 +2147,6 @@ class AFMFont {
2389
2147
  this.boundingBoxes = {};
2390
2148
  this.kernPairs = {};
2391
2149
  this.parse();
2392
- // todo: remove charWidths since appears to not be used
2393
2150
  this.charWidths = new Array(256);
2394
2151
  for (let char = 0; char <= 255; char++) {
2395
2152
  this.charWidths[char] = this.glyphWidths[characters[char]];
@@ -2496,21 +2253,17 @@ class PDFFont {
2496
2253
  return;
2497
2254
  }
2498
2255
  this.embed();
2499
- return this.embedded = true;
2256
+ this.embedded = true;
2500
2257
  }
2501
2258
  embed() {
2502
2259
  throw new Error('Must be implemented by subclasses');
2503
2260
  }
2504
- lineHeight(size, includeGap) {
2505
- if (includeGap == null) {
2506
- includeGap = false;
2507
- }
2261
+ lineHeight(size, includeGap = false) {
2508
2262
  const gap = includeGap ? this.lineGap : 0;
2509
2263
  return (this.ascender + gap - this.descender) / 1000 * size;
2510
2264
  }
2511
2265
  }
2512
2266
 
2513
- // This insanity is so bundlers can inline the font files
2514
2267
  const STANDARD_FONTS = {
2515
2268
  Courier() {
2516
2269
  return fs.readFileSync(__dirname + '/data/Courier.afm', 'utf8');
@@ -2638,8 +2391,6 @@ class EmbeddedFont extends PDFFont {
2638
2391
  }
2639
2392
  layoutRun(text, features) {
2640
2393
  const run = this.font.layout(text, features);
2641
-
2642
- // Normalize position values
2643
2394
  for (let i = 0; i < run.positions.length; i++) {
2644
2395
  const position = run.positions[i];
2645
2396
  for (let key in position) {
@@ -2662,16 +2413,12 @@ class EmbeddedFont extends PDFFont {
2662
2413
  return run;
2663
2414
  }
2664
2415
  layout(text, features, onlyWidth) {
2665
- // Skip the cache if any user defined features are applied
2666
2416
  if (features) {
2667
2417
  return this.layoutRun(text, features);
2668
2418
  }
2669
2419
  let glyphs = onlyWidth ? null : [];
2670
2420
  let positions = onlyWidth ? null : [];
2671
2421
  let advanceWidth = 0;
2672
-
2673
- // Split the string by words to increase cache efficiency.
2674
- // For this purpose, spaces and tabs are a good enough delimeter.
2675
2422
  let last = 0;
2676
2423
  let index = 0;
2677
2424
  while (index <= text.length) {
@@ -2733,17 +2480,15 @@ class EmbeddedFont extends PDFFont {
2733
2480
  if (1 <= familyClass && familyClass <= 7) {
2734
2481
  flags |= 1 << 1;
2735
2482
  }
2736
- flags |= 1 << 2; // assume the font uses non-latin characters
2483
+ flags |= 1 << 2;
2737
2484
  if (familyClass === 10) {
2738
2485
  flags |= 1 << 3;
2739
2486
  }
2740
2487
  if (this.font.head.macStyle.italic) {
2741
2488
  flags |= 1 << 6;
2742
2489
  }
2743
-
2744
- // generate a tag (6 uppercase letters. 17 is the char code offset from '0' to 'A'. 73 will map to 'Z')
2745
2490
  const tag = [1, 2, 3, 4, 5, 6].map(i => String.fromCharCode((this.id.charCodeAt(i) || 73) + 17)).join('');
2746
- const name = tag + '+' + this.font.postscriptName.replaceAll(' ', '_');
2491
+ const name = tag + '+' + this.font.postscriptName?.replaceAll(' ', '_');
2747
2492
  const {
2748
2493
  bbox
2749
2494
  } = this.font;
@@ -2758,8 +2503,7 @@ class EmbeddedFont extends PDFFont {
2758
2503
  CapHeight: (this.font.capHeight || this.font.ascent) * this.scale,
2759
2504
  XHeight: (this.font.xHeight || 0) * this.scale,
2760
2505
  StemV: 0
2761
- }); // not sure how to calculate this
2762
-
2506
+ });
2763
2507
  if (isCFF) {
2764
2508
  descriptor.data.FontFile3 = fontFile;
2765
2509
  } else {
@@ -2801,17 +2545,11 @@ class EmbeddedFont extends PDFFont {
2801
2545
  };
2802
2546
  return this.dictionary.end();
2803
2547
  }
2804
-
2805
- // Maps the glyph ids encoded in the PDF back to unicode strings
2806
- // Because of ligature substitutions and the like, there may be one or more
2807
- // unicode characters represented by each glyph.
2808
2548
  toUnicodeCmap() {
2809
2549
  const cmap = this.document.ref();
2810
2550
  const entries = [];
2811
2551
  for (let codePoints of this.unicode) {
2812
2552
  const encoded = [];
2813
-
2814
- // encode codePoints to utf16
2815
2553
  for (let value of codePoints) {
2816
2554
  if (value > 0xffff) {
2817
2555
  value -= 0x10000;
@@ -2878,31 +2616,26 @@ class PDFFontFactory {
2878
2616
  }
2879
2617
 
2880
2618
  const isEqualFont = (font1, font2) => {
2881
- // compare font checksum
2882
2619
  if (font1.font._tables?.head?.checkSumAdjustment !== font2.font._tables?.head?.checkSumAdjustment) {
2883
2620
  return false;
2884
2621
  }
2885
-
2886
- // compare font name table
2887
2622
  if (JSON.stringify(font1.font._tables?.name?.records) !== JSON.stringify(font2.font._tables?.name?.records)) {
2888
2623
  return false;
2889
2624
  }
2890
2625
  return true;
2891
2626
  };
2892
2627
  var FontsMixin = {
2893
- initFonts(defaultFont = 'Helvetica') {
2894
- // Lookup table for embedded fonts
2628
+ initFonts(defaultFont = 'Helvetica', defaultFontFamily = null, defaultFontSize = 12) {
2895
2629
  this._fontFamilies = {};
2896
2630
  this._fontCount = 0;
2897
-
2898
- // Font state
2899
- this._fontSize = 12;
2631
+ this._fontSource = defaultFont;
2632
+ this._fontFamily = defaultFontFamily;
2633
+ this._fontSize = defaultFontSize;
2900
2634
  this._font = null;
2635
+ this._remSize = defaultFontSize;
2901
2636
  this._registeredFonts = {};
2902
-
2903
- // Set the default font
2904
2637
  if (defaultFont) {
2905
- this.font(defaultFont);
2638
+ this.font(defaultFont, defaultFontFamily);
2906
2639
  }
2907
2640
  },
2908
2641
  font(src, family, size) {
@@ -2911,8 +2644,6 @@ var FontsMixin = {
2911
2644
  size = family;
2912
2645
  family = null;
2913
2646
  }
2914
-
2915
- // check registered fonts if src is a string
2916
2647
  if (typeof src === 'string' && this._registeredFonts[src]) {
2917
2648
  cacheKey = src;
2918
2649
  ({
@@ -2925,28 +2656,21 @@ var FontsMixin = {
2925
2656
  cacheKey = null;
2926
2657
  }
2927
2658
  }
2659
+ this._fontSource = src;
2660
+ this._fontFamily = family;
2928
2661
  if (size != null) {
2929
2662
  this.fontSize(size);
2930
2663
  }
2931
-
2932
- // fast path: check if the font is already in the PDF
2933
2664
  if (font = this._fontFamilies[cacheKey]) {
2934
2665
  this._font = font;
2935
2666
  return this;
2936
2667
  }
2937
-
2938
- // load the font
2939
2668
  const id = `F${++this._fontCount}`;
2940
2669
  this._font = PDFFontFactory.open(this, src, family, id);
2941
-
2942
- // check for existing font familes with the same name already in the PDF
2943
- // useful if the font was passed as a buffer
2944
2670
  if ((font = this._fontFamilies[this._font.name]) && isEqualFont(this._font, font)) {
2945
2671
  this._font = font;
2946
2672
  return this;
2947
2673
  }
2948
-
2949
- // save the font for reuse later
2950
2674
  if (cacheKey) {
2951
2675
  this._fontFamilies[cacheKey] = this._font;
2952
2676
  }
@@ -2956,13 +2680,10 @@ var FontsMixin = {
2956
2680
  return this;
2957
2681
  },
2958
2682
  fontSize(_fontSize) {
2959
- this._fontSize = _fontSize;
2683
+ this._fontSize = this.sizeToPoint(_fontSize);
2960
2684
  return this;
2961
2685
  },
2962
2686
  currentLineHeight(includeGap) {
2963
- if (includeGap == null) {
2964
- includeGap = false;
2965
- }
2966
2687
  return this._font.lineHeight(this._fontSize, includeGap);
2967
2688
  },
2968
2689
  registerFont(name, src, family) {
@@ -2971,6 +2692,64 @@ var FontsMixin = {
2971
2692
  family
2972
2693
  };
2973
2694
  return this;
2695
+ },
2696
+ sizeToPoint(size, defaultValue = 0, page = this.page, percentageWidth = undefined) {
2697
+ if (!percentageWidth) percentageWidth = this._fontSize;
2698
+ if (typeof defaultValue !== 'number') defaultValue = this.sizeToPoint(defaultValue);
2699
+ if (size === undefined) return defaultValue;
2700
+ if (typeof size === 'number') return size;
2701
+ if (typeof size === 'boolean') return Number(size);
2702
+ const match = String(size).match(/((\d+)?(\.\d+)?)(em|in|px|cm|mm|pc|ex|ch|rem|vw|vh|vmin|vmax|%|pt)?/);
2703
+ if (!match) throw new Error(`Unsupported size '${size}'`);
2704
+ let multiplier;
2705
+ switch (match[4]) {
2706
+ case 'em':
2707
+ multiplier = this._fontSize;
2708
+ break;
2709
+ case 'in':
2710
+ multiplier = IN_TO_PT;
2711
+ break;
2712
+ case 'px':
2713
+ multiplier = PX_TO_IN * IN_TO_PT;
2714
+ break;
2715
+ case 'cm':
2716
+ multiplier = CM_TO_IN * IN_TO_PT;
2717
+ break;
2718
+ case 'mm':
2719
+ multiplier = MM_TO_CM * CM_TO_IN * IN_TO_PT;
2720
+ break;
2721
+ case 'pc':
2722
+ multiplier = PC_TO_PT;
2723
+ break;
2724
+ case 'ex':
2725
+ multiplier = this.currentLineHeight();
2726
+ break;
2727
+ case 'ch':
2728
+ multiplier = this.widthOfString('0');
2729
+ break;
2730
+ case 'rem':
2731
+ multiplier = this._remSize;
2732
+ break;
2733
+ case 'vw':
2734
+ multiplier = page.width / 100;
2735
+ break;
2736
+ case 'vh':
2737
+ multiplier = page.height / 100;
2738
+ break;
2739
+ case 'vmin':
2740
+ multiplier = Math.min(page.width, page.height) / 100;
2741
+ break;
2742
+ case 'vmax':
2743
+ multiplier = Math.max(page.width, page.height) / 100;
2744
+ break;
2745
+ case '%':
2746
+ multiplier = percentageWidth / 100;
2747
+ break;
2748
+ case 'pt':
2749
+ default:
2750
+ multiplier = 1;
2751
+ }
2752
+ return multiplier * Number(match[1]);
2974
2753
  }
2975
2754
  };
2976
2755
 
@@ -2985,7 +2764,7 @@ class LineWrapper extends events.EventEmitter {
2985
2764
  this.characterSpacing = (options.characterSpacing || 0) * this.horizontalScaling / 100;
2986
2765
  this.wordSpacing = (options.wordSpacing === 0) * this.horizontalScaling / 100;
2987
2766
  this.columns = options.columns || 1;
2988
- this.columnGap = (options.columnGap != null ? options.columnGap : 18) * this.horizontalScaling / 100; // 1/4 inch
2767
+ this.columnGap = (options.columnGap != null ? options.columnGap : 18) * this.horizontalScaling / 100;
2989
2768
  this.lineWidth = (options.width * this.horizontalScaling / 100 - this.columnGap * (this.columns - 1)) / this.columns;
2990
2769
  this.spaceLeft = this.lineWidth;
2991
2770
  this.startX = this.document.x;
@@ -2994,44 +2773,30 @@ class LineWrapper extends events.EventEmitter {
2994
2773
  this.ellipsis = options.ellipsis;
2995
2774
  this.continuedX = 0;
2996
2775
  this.features = options.features;
2997
-
2998
- // calculate the maximum Y position the text can appear at
2999
2776
  if (options.height != null) {
3000
2777
  this.height = options.height;
3001
- this.maxY = this.startY + options.height;
2778
+ this.maxY = PDFNumber(this.startY + options.height);
3002
2779
  } else {
3003
- this.maxY = this.document.page.maxY();
2780
+ this.maxY = PDFNumber(this.document.page.maxY());
3004
2781
  }
3005
-
3006
- // handle paragraph indents
3007
2782
  this.on('firstLine', options => {
3008
- // if this is the first line of the text segment, and
3009
- // we're continuing where we left off, indent that much
3010
- // otherwise use the user specified indent option
3011
2783
  const indent = this.continuedX || this.indent;
3012
2784
  this.document.x += indent;
3013
2785
  this.lineWidth -= indent;
3014
-
3015
- // if indentAllLines is set to true
3016
- // we're not resetting the indentation for this paragraph after the first line
3017
2786
  if (options.indentAllLines) {
3018
2787
  return;
3019
2788
  }
3020
-
3021
- // otherwise we start the next line without indent
3022
- return this.once('line', () => {
2789
+ this.once('line', () => {
3023
2790
  this.document.x -= indent;
3024
2791
  this.lineWidth += indent;
3025
2792
  if (options.continued && !this.continuedX) {
3026
2793
  this.continuedX = this.indent;
3027
2794
  }
3028
2795
  if (!options.continued) {
3029
- return this.continuedX = 0;
2796
+ this.continuedX = 0;
3030
2797
  }
3031
2798
  });
3032
2799
  });
3033
-
3034
- // handle left aligning last lines of paragraphs
3035
2800
  this.on('lastLine', options => {
3036
2801
  const {
3037
2802
  align
@@ -3040,7 +2805,7 @@ class LineWrapper extends events.EventEmitter {
3040
2805
  options.align = 'left';
3041
2806
  }
3042
2807
  this.lastLine = true;
3043
- return this.once('line', () => {
2808
+ this.once('line', () => {
3044
2809
  this.document.y += options.paragraphGap || 0;
3045
2810
  options.align = align;
3046
2811
  return this.lastLine = false;
@@ -3057,7 +2822,6 @@ class LineWrapper extends events.EventEmitter {
3057
2822
  return w + this.wordWidth(HYPHEN) <= this.spaceLeft;
3058
2823
  }
3059
2824
  eachWord(text, fn) {
3060
- // setup a unicode line breaker
3061
2825
  let bk;
3062
2826
  const breaker = new LineBreaker(text);
3063
2827
  let last = null;
@@ -3066,19 +2830,12 @@ class LineWrapper extends events.EventEmitter {
3066
2830
  var shouldContinue;
3067
2831
  let word = text.slice((last != null ? last.position : undefined) || 0, bk.position);
3068
2832
  let w = wordWidths[word] != null ? wordWidths[word] : wordWidths[word] = this.wordWidth(word);
3069
-
3070
- // if the word is longer than the whole line, chop it up
3071
- // TODO: break by grapheme clusters, not JS string characters
3072
2833
  if (w > this.lineWidth + this.continuedX) {
3073
- // make some fake break objects
3074
2834
  let lbk = last;
3075
2835
  const fbk = {};
3076
2836
  while (word.length) {
3077
- // fit as much of the word as possible into the space we have
3078
2837
  var l, mightGrow;
3079
2838
  if (w > this.spaceLeft) {
3080
- // start our check at the end of our available space - this method is faster than a loop of each character and it resolves
3081
- // an issue with long loops when processing massive words, such as a huge number of spaces
3082
2839
  l = Math.ceil(this.spaceLeft / (w / word.length));
3083
2840
  w = this.wordWidth(word.slice(0, l));
3084
2841
  mightGrow = w <= this.spaceLeft && l < word.length;
@@ -3086,7 +2843,6 @@ class LineWrapper extends events.EventEmitter {
3086
2843
  l = word.length;
3087
2844
  }
3088
2845
  let mustShrink = w > this.spaceLeft && l > 0;
3089
- // shrink or grow word as necessary after our near-guess above
3090
2846
  while (mustShrink || mightGrow) {
3091
2847
  if (mustShrink) {
3092
2848
  w = this.wordWidth(word.slice(0, --l));
@@ -3097,20 +2853,14 @@ class LineWrapper extends events.EventEmitter {
3097
2853
  mightGrow = w <= this.spaceLeft && l < word.length;
3098
2854
  }
3099
2855
  }
3100
-
3101
- // check for the edge case where a single character cannot fit into a line.
3102
2856
  if (l === 0 && this.spaceLeft === this.lineWidth) {
3103
2857
  l = 1;
3104
2858
  }
3105
-
3106
- // send a required break unless this is the last piece and a linebreak is not specified
3107
2859
  fbk.required = bk.required || l < word.length;
3108
2860
  shouldContinue = fn(word.slice(0, l), w, fbk, lbk);
3109
2861
  lbk = {
3110
2862
  required: false
3111
2863
  };
3112
-
3113
- // get the remaining piece of the word
3114
2864
  word = word.slice(l);
3115
2865
  w = this.wordWidth(word);
3116
2866
  if (shouldContinue === false) {
@@ -3118,7 +2868,6 @@ class LineWrapper extends events.EventEmitter {
3118
2868
  }
3119
2869
  }
3120
2870
  } else {
3121
- // otherwise just emit the break as it was given to us
3122
2871
  shouldContinue = fn(word, w, bk, last);
3123
2872
  }
3124
2873
  if (shouldContinue === false) {
@@ -3128,7 +2877,6 @@ class LineWrapper extends events.EventEmitter {
3128
2877
  }
3129
2878
  }
3130
2879
  wrap(text, options) {
3131
- // override options from previous continued fragments
3132
2880
  this.horizontalScaling = options.horizontalScaling || 100;
3133
2881
  if (options.indent != null) {
3134
2882
  this.indent = options.indent * this.horizontalScaling / 100;
@@ -3142,10 +2890,6 @@ class LineWrapper extends events.EventEmitter {
3142
2890
  if (options.ellipsis != null) {
3143
2891
  this.ellipsis = options.ellipsis;
3144
2892
  }
3145
-
3146
- // make sure we're actually on the page
3147
- // and that the first line of is never by
3148
- // itself at the bottom of a page (orphans)
3149
2893
  const nextY = this.document.y + this.document.currentLineHeight(true);
3150
2894
  if (this.document.y > this.maxY || nextY > this.maxY) {
3151
2895
  this.nextSection();
@@ -3156,7 +2900,7 @@ class LineWrapper extends events.EventEmitter {
3156
2900
  let lc = 0;
3157
2901
  let {
3158
2902
  y
3159
- } = this.document; // used to reset Y pos if options.continued (below)
2903
+ } = this.document;
3160
2904
  const emitLine = () => {
3161
2905
  options.textWidth = textWidth + this.wordSpacing * (wc - 1);
3162
2906
  options.wordCount = wc;
@@ -3179,23 +2923,17 @@ class LineWrapper extends events.EventEmitter {
3179
2923
  wc++;
3180
2924
  }
3181
2925
  if (bk.required || !this.canFit(word, w)) {
3182
- // if the user specified a max height and an ellipsis, and is about to pass the
3183
- // max height and max columns after the next line, append the ellipsis
3184
2926
  const lh = this.document.currentLineHeight(true);
3185
- if (this.height != null && this.ellipsis && this.document.y + lh * 2 > this.maxY && this.column >= this.columns) {
2927
+ if (this.height != null && this.ellipsis && PDFNumber(this.document.y + lh * 2) > this.maxY && this.column >= this.columns) {
3186
2928
  if (this.ellipsis === true) {
3187
2929
  this.ellipsis = '…';
3188
- } // map default ellipsis character
2930
+ }
3189
2931
  buffer = buffer.replace(/\s+$/, '');
3190
2932
  textWidth = this.wordWidth(buffer + this.ellipsis);
3191
-
3192
- // remove characters from the buffer until the ellipsis fits
3193
- // to avoid infinite loop need to stop while-loop if buffer is empty string
3194
2933
  while (buffer && textWidth > this.lineWidth) {
3195
2934
  buffer = buffer.slice(0, -1).replace(/\s+$/, '');
3196
2935
  textWidth = this.wordWidth(buffer + this.ellipsis);
3197
2936
  }
3198
- // need to add ellipsis only if there is enough space for it
3199
2937
  if (textWidth <= this.lineWidth) {
3200
2938
  buffer = buffer + this.ellipsis;
3201
2939
  }
@@ -3210,35 +2948,25 @@ class LineWrapper extends events.EventEmitter {
3210
2948
  }
3211
2949
  this.emit('lastLine', options, this);
3212
2950
  }
3213
-
3214
- // Previous entry is a soft hyphen - add visible hyphen.
3215
2951
  if (buffer[buffer.length - 1] == SOFT_HYPHEN) {
3216
2952
  buffer = buffer.slice(0, -1) + HYPHEN;
3217
2953
  this.spaceLeft -= this.wordWidth(HYPHEN);
3218
2954
  }
3219
2955
  emitLine();
3220
-
3221
- // if we've reached the edge of the page,
3222
- // continue on a new page or column
3223
- if (this.document.y + lh > this.maxY) {
2956
+ if (PDFNumber(this.document.y + lh) > this.maxY) {
3224
2957
  const shouldContinue = this.nextSection();
3225
-
3226
- // stop if we reached the maximum height
3227
2958
  if (!shouldContinue) {
3228
2959
  wc = 0;
3229
2960
  buffer = '';
3230
2961
  return false;
3231
2962
  }
3232
2963
  }
3233
-
3234
- // reset the space left and buffer
3235
2964
  if (bk.required) {
3236
2965
  this.spaceLeft = this.lineWidth;
3237
2966
  buffer = '';
3238
2967
  textWidth = 0;
3239
2968
  return wc = 0;
3240
2969
  } else {
3241
- // reset the space left and buffer
3242
2970
  this.spaceLeft = this.lineWidth - w;
3243
2971
  buffer = word;
3244
2972
  textWidth = w;
@@ -3253,25 +2981,19 @@ class LineWrapper extends events.EventEmitter {
3253
2981
  emitLine();
3254
2982
  }
3255
2983
  this.emit('sectionEnd', options, this);
3256
-
3257
- // if the wrap is set to be continued, save the X position
3258
- // to start the first line of the next segment at, and reset
3259
- // the y position
3260
2984
  if (options.continued === true) {
3261
2985
  if (lc > 1) {
3262
2986
  this.continuedX = 0;
3263
2987
  }
3264
2988
  this.continuedX += options.textWidth || 0;
3265
- return this.document.y = y;
2989
+ this.document.y = y;
3266
2990
  } else {
3267
- return this.document.x = this.startX;
2991
+ this.document.x = this.startX;
3268
2992
  }
3269
2993
  }
3270
2994
  nextSection(options) {
3271
2995
  this.emit('sectionEnd', options, this);
3272
2996
  if (++this.column > this.columns) {
3273
- // if a max height was specified by the user, we're done.
3274
- // otherwise, the default is to make a new page at the bottom.
3275
2997
  if (this.height != null) {
3276
2998
  return false;
3277
2999
  }
@@ -3300,10 +3022,9 @@ const {
3300
3022
  var TextMixin = {
3301
3023
  initText() {
3302
3024
  this._line = this._line.bind(this);
3303
- // Current coordinates
3304
3025
  this.x = 0;
3305
3026
  this.y = 0;
3306
- return this._lineGap = 0;
3027
+ this._lineGap = 0;
3307
3028
  },
3308
3029
  lineGap(_lineGap) {
3309
3030
  this._lineGap = _lineGap;
@@ -3325,11 +3046,7 @@ var TextMixin = {
3325
3046
  },
3326
3047
  _text(text, x, y, options, lineCallback) {
3327
3048
  options = this._initOptions(x, y, options);
3328
-
3329
- // Convert text to a string
3330
3049
  text = text == null ? '' : `${text}`;
3331
-
3332
- // if the wordSpacing option is specified, remove multiple consecutive spaces
3333
3050
  if (options.wordSpacing) {
3334
3051
  text = text.replace(/\s{2,}/g, ' ');
3335
3052
  }
@@ -3338,8 +3055,12 @@ var TextMixin = {
3338
3055
  options.structParent.add(this.struct(options.structType || 'P', [this.markStructureContent(options.structType || 'P')]));
3339
3056
  }
3340
3057
  };
3341
-
3342
- // word wrapping
3058
+ if (options.rotation !== 0) {
3059
+ this.save();
3060
+ this.rotate(-options.rotation, {
3061
+ origin: [this.x, this.y]
3062
+ });
3063
+ }
3343
3064
  if (options.width) {
3344
3065
  let wrapper = this._wrapper;
3345
3066
  if (!wrapper) {
@@ -3350,14 +3071,13 @@ var TextMixin = {
3350
3071
  this._wrapper = options.continued ? wrapper : null;
3351
3072
  this._textOptions = options.continued ? options : null;
3352
3073
  wrapper.wrap(text, options);
3353
-
3354
- // render paragraphs as single lines
3355
3074
  } else {
3356
3075
  for (let line of text.split('\n')) {
3357
3076
  addStructure();
3358
3077
  lineCallback(line, options);
3359
3078
  }
3360
3079
  }
3080
+ if (options.rotation !== 0) this.restore();
3361
3081
  return this;
3362
3082
  },
3363
3083
  text(text, x, y, options) {
@@ -3367,17 +3087,108 @@ var TextMixin = {
3367
3087
  const horizontalScaling = options.horizontalScaling || 100;
3368
3088
  return (this._font.widthOfString(string, this._fontSize, options.features) + (options.characterSpacing || 0) * (string.length - 1)) * horizontalScaling / 100;
3369
3089
  },
3090
+ boundsOfString(string, x, y, options) {
3091
+ options = this._initOptions(x, y, options);
3092
+ ({
3093
+ x,
3094
+ y
3095
+ } = this);
3096
+ const lineGap = options.lineGap ?? this._lineGap ?? 0;
3097
+ const lineHeight = this.currentLineHeight(true) + lineGap;
3098
+ let contentWidth = 0;
3099
+ string = String(string ?? '');
3100
+ if (options.wordSpacing) {
3101
+ string = string.replace(/\s{2,}/g, ' ');
3102
+ }
3103
+ if (options.width) {
3104
+ let wrapper = new LineWrapper(this, options);
3105
+ wrapper.on('line', (text, options) => {
3106
+ this.y += lineHeight;
3107
+ text = text.replace(/\n/g, '');
3108
+ if (text.length) {
3109
+ let wordSpacing = options.wordSpacing ?? 0;
3110
+ const characterSpacing = options.characterSpacing ?? 0;
3111
+ if (options.width && options.align === 'justify') {
3112
+ const words = text.trim().split(/\s+/);
3113
+ const textWidth = this.widthOfString(text.replace(/\s+/g, ''), options);
3114
+ const spaceWidth = this.widthOfString(' ') + characterSpacing;
3115
+ wordSpacing = Math.max(0, (options.lineWidth - textWidth) / Math.max(1, words.length - 1) - spaceWidth);
3116
+ }
3117
+ contentWidth = Math.max(contentWidth, options.textWidth + wordSpacing * (options.wordCount - 1) + characterSpacing * (text.length - 1));
3118
+ }
3119
+ });
3120
+ wrapper.wrap(string, options);
3121
+ } else {
3122
+ for (let line of string.split('\n')) {
3123
+ const lineWidth = this.widthOfString(line, options);
3124
+ this.y += lineHeight;
3125
+ contentWidth = Math.max(contentWidth, lineWidth);
3126
+ }
3127
+ }
3128
+ let contentHeight = this.y - y;
3129
+ if (options.height) contentHeight = Math.min(contentHeight, options.height);
3130
+ this.x = x;
3131
+ this.y = y;
3132
+ if (options.rotation === 0) {
3133
+ return {
3134
+ x,
3135
+ y,
3136
+ width: contentWidth,
3137
+ height: contentHeight
3138
+ };
3139
+ } else if (options.rotation === 90) {
3140
+ return {
3141
+ x: x,
3142
+ y: y - contentWidth,
3143
+ width: contentHeight,
3144
+ height: contentWidth
3145
+ };
3146
+ } else if (options.rotation === 180) {
3147
+ return {
3148
+ x: x - contentWidth,
3149
+ y: y - contentHeight,
3150
+ width: contentWidth,
3151
+ height: contentHeight
3152
+ };
3153
+ } else if (options.rotation === 270) {
3154
+ return {
3155
+ x: x - contentHeight,
3156
+ y: y,
3157
+ width: contentHeight,
3158
+ height: contentWidth
3159
+ };
3160
+ }
3161
+ const cos = cosine(options.rotation);
3162
+ const sin = sine(options.rotation);
3163
+ const x1 = x;
3164
+ const y1 = y;
3165
+ const x2 = x + contentWidth * cos;
3166
+ const y2 = y - contentWidth * sin;
3167
+ const x3 = x + contentWidth * cos + contentHeight * sin;
3168
+ const y3 = y - contentWidth * sin + contentHeight * cos;
3169
+ const x4 = x + contentHeight * sin;
3170
+ const y4 = y + contentHeight * cos;
3171
+ const xMin = Math.min(x1, x2, x3, x4);
3172
+ const xMax = Math.max(x1, x2, x3, x4);
3173
+ const yMin = Math.min(y1, y2, y3, y4);
3174
+ const yMax = Math.max(y1, y2, y3, y4);
3175
+ return {
3176
+ x: xMin,
3177
+ y: yMin,
3178
+ width: xMax - xMin,
3179
+ height: yMax - yMin
3180
+ };
3181
+ },
3370
3182
  heightOfString(text, options) {
3371
3183
  const {
3372
3184
  x,
3373
3185
  y
3374
3186
  } = this;
3375
3187
  options = this._initOptions(options);
3376
- options.height = Infinity; // don't break pages
3377
-
3188
+ options.height = Infinity;
3378
3189
  const lineGap = options.lineGap || this._lineGap || 0;
3379
3190
  this._text(text, this.x, this.y, options, () => {
3380
- return this.y += this.currentLineHeight(true) + lineGap;
3191
+ this.y += this.currentLineHeight(true) + lineGap;
3381
3192
  });
3382
3193
  const height = this.y - y;
3383
3194
  this.x = x;
@@ -3475,12 +3286,12 @@ var TextMixin = {
3475
3286
  wrapper.on('sectionStart', () => {
3476
3287
  const pos = indent + itemIndent * (level - 1);
3477
3288
  this.x += pos;
3478
- return wrapper.lineWidth -= pos;
3289
+ wrapper.lineWidth -= pos;
3479
3290
  });
3480
3291
  wrapper.on('sectionEnd', () => {
3481
3292
  const pos = indent + itemIndent * (level - 1);
3482
3293
  this.x -= pos;
3483
- return wrapper.lineWidth += pos;
3294
+ wrapper.lineWidth += pos;
3484
3295
  });
3485
3296
  wrapper.wrap(listItem, options);
3486
3297
  };
@@ -3494,11 +3305,7 @@ var TextMixin = {
3494
3305
  options = x;
3495
3306
  x = null;
3496
3307
  }
3497
-
3498
- // clone options object
3499
3308
  const result = Object.assign({}, options);
3500
-
3501
- // extend options with previous values for continued text
3502
3309
  if (this._textOptions) {
3503
3310
  for (let key in this._textOptions) {
3504
3311
  const val = this._textOptions[key];
@@ -3509,16 +3316,12 @@ var TextMixin = {
3509
3316
  }
3510
3317
  }
3511
3318
  }
3512
-
3513
- // Update the current position
3514
3319
  if (x != null) {
3515
3320
  this.x = x;
3516
3321
  }
3517
3322
  if (y != null) {
3518
3323
  this.y = y;
3519
3324
  }
3520
-
3521
- // wrap to margins if no x or y position passed
3522
3325
  if (result.lineBreak !== false) {
3523
3326
  if (result.width == null) {
3524
3327
  result.width = this.page.width - this.x - this.page.margins.right;
@@ -3530,17 +3333,18 @@ var TextMixin = {
3530
3333
  }
3531
3334
  if (result.columnGap == null) {
3532
3335
  result.columnGap = 18;
3533
- } // 1/4 inch
3534
-
3336
+ }
3337
+ result.rotation = Number(options.rotation ?? 0) % 360;
3338
+ if (result.rotation < 0) result.rotation += 360;
3535
3339
  return result;
3536
3340
  },
3537
3341
  _line(text, options = {}, wrapper) {
3538
3342
  this._fragment(text, this.x, this.y, options);
3539
3343
  const lineGap = options.lineGap || this._lineGap || 0;
3540
3344
  if (!wrapper) {
3541
- return this.x += this.widthOfString(text, options);
3345
+ this.x += this.widthOfString(text, options);
3542
3346
  } else {
3543
- return this.y += this.currentLineHeight(true) + lineGap;
3347
+ this.y += this.currentLineHeight(true) + lineGap;
3544
3348
  }
3545
3349
  },
3546
3350
  _fragment(text, x, y, options) {
@@ -3549,14 +3353,10 @@ var TextMixin = {
3549
3353
  if (text.length === 0) {
3550
3354
  return;
3551
3355
  }
3552
-
3553
- // handle options
3554
3356
  const align = options.align || 'left';
3555
3357
  let wordSpacing = options.wordSpacing || 0;
3556
3358
  const characterSpacing = options.characterSpacing || 0;
3557
3359
  const horizontalScaling = options.horizontalScaling || 100;
3558
-
3559
- // text alignments
3560
3360
  if (options.width) {
3561
3361
  switch (align) {
3562
3362
  case 'right':
@@ -3567,7 +3367,6 @@ var TextMixin = {
3567
3367
  x += options.lineWidth / 2 - options.textWidth / 2;
3568
3368
  break;
3569
3369
  case 'justify':
3570
- // calculate the word spacing value
3571
3370
  words = text.trim().split(/\s+/);
3572
3371
  textWidth = this.widthOfString(text.replace(/\s+/g, ''), options);
3573
3372
  var spaceWidth = this.widthOfString(' ') + characterSpacing;
@@ -3575,8 +3374,6 @@ var TextMixin = {
3575
3374
  break;
3576
3375
  }
3577
3376
  }
3578
-
3579
- // text baseline alignments based on http://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling
3580
3377
  if (typeof options.baseline === 'number') {
3581
3378
  dy = -options.baseline;
3582
3379
  } else {
@@ -3609,11 +3406,7 @@ var TextMixin = {
3609
3406
  }
3610
3407
  dy = dy / 1000 * this._fontSize;
3611
3408
  }
3612
-
3613
- // calculate the actual rendered width of the string after word and character spacing
3614
3409
  const renderedWidth = options.textWidth + wordSpacing * (options.wordCount - 1) + characterSpacing * (text.length - 1);
3615
-
3616
- // create link annotations if the link option is given
3617
3410
  if (options.link != null) {
3618
3411
  this.link(x, y, renderedWidth, this.currentLineHeight(), options.link);
3619
3412
  }
@@ -3623,8 +3416,6 @@ var TextMixin = {
3623
3416
  if (options.destination != null) {
3624
3417
  this.addNamedDestination(options.destination, 'XYZ', x, y, null);
3625
3418
  }
3626
-
3627
- // create underline
3628
3419
  if (options.underline) {
3629
3420
  this.save();
3630
3421
  if (!options.stroke) {
@@ -3638,8 +3429,6 @@ var TextMixin = {
3638
3429
  this.stroke();
3639
3430
  this.restore();
3640
3431
  }
3641
-
3642
- // create strikethrough line
3643
3432
  if (options.strike) {
3644
3433
  this.save();
3645
3434
  if (!options.stroke) {
@@ -3654,8 +3443,6 @@ var TextMixin = {
3654
3443
  this.restore();
3655
3444
  }
3656
3445
  this.save();
3657
-
3658
- // oblique (angle in degrees or boolean)
3659
3446
  if (options.oblique) {
3660
3447
  let skew;
3661
3448
  if (typeof options.oblique === 'number') {
@@ -3667,45 +3454,24 @@ var TextMixin = {
3667
3454
  this.transform(1, 0, skew, 1, -skew * dy, 0);
3668
3455
  this.transform(1, 0, 0, 1, -x, -y);
3669
3456
  }
3670
-
3671
- // flip coordinate system
3672
3457
  this.transform(1, 0, 0, -1, 0, this.page.height);
3673
3458
  y = this.page.height - y - dy;
3674
-
3675
- // add current font to page if necessary
3676
3459
  if (this.page.fonts[this._font.id] == null) {
3677
3460
  this.page.fonts[this._font.id] = this._font.ref();
3678
3461
  }
3679
-
3680
- // begin the text object
3681
3462
  this.addContent('BT');
3682
-
3683
- // text position
3684
3463
  this.addContent(`1 0 0 1 ${number(x)} ${number(y)} Tm`);
3685
-
3686
- // font and font size
3687
3464
  this.addContent(`/${this._font.id} ${number(this._fontSize)} Tf`);
3688
-
3689
- // rendering mode
3690
3465
  const mode = options.fill && options.stroke ? 2 : options.stroke ? 1 : 0;
3691
3466
  if (mode) {
3692
3467
  this.addContent(`${mode} Tr`);
3693
3468
  }
3694
-
3695
- // Character spacing
3696
3469
  if (characterSpacing) {
3697
3470
  this.addContent(`${number(characterSpacing)} Tc`);
3698
3471
  }
3699
-
3700
- // Horizontal scaling
3701
3472
  if (horizontalScaling !== 100) {
3702
3473
  this.addContent(`${horizontalScaling} Tz`);
3703
3474
  }
3704
-
3705
- // Add the actual text
3706
- // If we have a word spacing value, we need to encode each word separately
3707
- // since the normal Tw operator only works on character code 32, which isn't
3708
- // used for embedded fonts.
3709
3475
  if (wordSpacing) {
3710
3476
  words = text.trim().split(/\s+/);
3711
3477
  wordSpacing += this.widthOfString(' ') + characterSpacing;
@@ -3716,9 +3482,6 @@ var TextMixin = {
3716
3482
  const [encodedWord, positionsWord] = this._font.encode(word, options.features);
3717
3483
  encoded = encoded.concat(encodedWord);
3718
3484
  positions = positions.concat(positionsWord);
3719
-
3720
- // add the word spacing to the end of the word
3721
- // clone object because of cache
3722
3485
  const space = {};
3723
3486
  const object = positions[positions.length - 1];
3724
3487
  for (let key in object) {
@@ -3735,60 +3498,42 @@ var TextMixin = {
3735
3498
  const commands = [];
3736
3499
  let last = 0;
3737
3500
  let hadOffset = false;
3738
-
3739
- // Adds a segment of text to the TJ command buffer
3740
3501
  const addSegment = cur => {
3741
3502
  if (last < cur) {
3742
3503
  const hex = encoded.slice(last, cur).join('');
3743
3504
  const advance = positions[cur - 1].xAdvance - positions[cur - 1].advanceWidth;
3744
3505
  commands.push(`<${hex}> ${number(-advance)}`);
3745
3506
  }
3746
- return last = cur;
3507
+ last = cur;
3747
3508
  };
3748
-
3749
- // Flushes the current TJ commands to the output stream
3750
3509
  const flush = i => {
3751
3510
  addSegment(i);
3752
3511
  if (commands.length > 0) {
3753
3512
  this.addContent(`[${commands.join(' ')}] TJ`);
3754
- return commands.length = 0;
3513
+ commands.length = 0;
3755
3514
  }
3756
3515
  };
3757
3516
  for (i = 0; i < positions.length; i++) {
3758
- // If we have an x or y offset, we have to break out of the current TJ command
3759
- // so we can move the text position.
3760
3517
  const pos = positions[i];
3761
3518
  if (pos.xOffset || pos.yOffset) {
3762
- // Flush the current buffer
3763
3519
  flush(i);
3764
-
3765
- // Move the text position and flush just the current character
3766
3520
  this.addContent(`1 0 0 1 ${number(x + pos.xOffset * scale)} ${number(y + pos.yOffset * scale)} Tm`);
3767
3521
  flush(i + 1);
3768
3522
  hadOffset = true;
3769
3523
  } else {
3770
- // If the last character had an offset, reset the text position
3771
3524
  if (hadOffset) {
3772
3525
  this.addContent(`1 0 0 1 ${number(x)} ${number(y)} Tm`);
3773
3526
  hadOffset = false;
3774
3527
  }
3775
-
3776
- // Group segments that don't have any advance adjustments
3777
3528
  if (pos.xAdvance - pos.advanceWidth !== 0) {
3778
3529
  addSegment(i + 1);
3779
3530
  }
3780
3531
  }
3781
3532
  x += pos.xAdvance * scale;
3782
3533
  }
3783
-
3784
- // Flush any remaining commands
3785
3534
  flush(i);
3786
-
3787
- // end the text object
3788
3535
  this.addContent('ET');
3789
-
3790
- // restore flipped coordinate system
3791
- return this.restore();
3536
+ this.restore();
3792
3537
  }
3793
3538
  };
3794
3539
 
@@ -3806,8 +3551,6 @@ class JPEG {
3806
3551
  if (this.data.readUInt16BE(0) !== 0xffd8) {
3807
3552
  throw 'SOI not found in JPEG';
3808
3553
  }
3809
-
3810
- // Parse the EXIF orientation
3811
3554
  this.orientation = exif.fromBuffer(this.data).Orientation || 1;
3812
3555
  let pos = 2;
3813
3556
  while (pos < this.data.length) {
@@ -3844,16 +3587,10 @@ class JPEG {
3844
3587
  ColorSpace: this.colorSpace,
3845
3588
  Filter: 'DCTDecode'
3846
3589
  });
3847
-
3848
- // add extra decode params for CMYK images. By swapping the
3849
- // min and max values from the default, we invert the colors. See
3850
- // section 4.8.4 of the spec.
3851
3590
  if (this.colorSpace === 'DeviceCMYK') {
3852
3591
  this.obj.data['Decode'] = [1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0];
3853
3592
  }
3854
3593
  this.obj.end(this.data);
3855
-
3856
- // free memory
3857
3594
  return this.data = null;
3858
3595
  }
3859
3596
  }
@@ -3896,24 +3633,14 @@ class PNGImage {
3896
3633
  if (this.image.palette.length === 0) {
3897
3634
  this.obj.data['ColorSpace'] = this.image.colorSpace;
3898
3635
  } else {
3899
- // embed the color palette in the PDF as an object stream
3900
3636
  const palette = this.document.ref();
3901
3637
  palette.end(Buffer.from(this.image.palette));
3902
-
3903
- // build the color space array for the image
3904
3638
  this.obj.data['ColorSpace'] = ['Indexed', 'DeviceRGB', this.image.palette.length / 3 - 1, palette];
3905
3639
  }
3906
-
3907
- // For PNG color types 0, 2 and 3, the transparency data is stored in
3908
- // a dedicated PNG chunk.
3909
3640
  if (this.image.transparency.grayscale != null) {
3910
- // Use Color Key Masking (spec section 4.8.5)
3911
- // An array with N elements, where N is two times the number of color components.
3912
3641
  const val = this.image.transparency.grayscale;
3913
3642
  this.obj.data['Mask'] = [val, val];
3914
3643
  } else if (this.image.transparency.rgb) {
3915
- // Use Color Key Masking (spec section 4.8.5)
3916
- // An array with N elements, where N is two times the number of color components.
3917
3644
  const {
3918
3645
  rgb
3919
3646
  } = this.image.transparency;
@@ -3923,14 +3650,9 @@ class PNGImage {
3923
3650
  }
3924
3651
  this.obj.data['Mask'] = mask;
3925
3652
  } else if (this.image.transparency.indexed) {
3926
- // Create a transparency SMask for the image based on the data
3927
- // in the PLTE and tRNS sections. See below for details on SMasks.
3928
3653
  dataDecoded = true;
3929
3654
  return this.loadIndexedAlphaChannel();
3930
3655
  } else if (hasAlphaChannel) {
3931
- // For PNG color types 4 and 6, the transparency data is stored as a alpha
3932
- // channel mixed in with the main image data. Separate this data out into an
3933
- // SMask object and store it separately in the PDF.
3934
3656
  dataDecoded = true;
3935
3657
  return this.splitAlphaChannel();
3936
3658
  }
@@ -3954,11 +3676,7 @@ class PNGImage {
3954
3676
  sMask.end(this.alphaChannel);
3955
3677
  this.obj.data['SMask'] = sMask;
3956
3678
  }
3957
-
3958
- // add the actual image data
3959
3679
  this.obj.end(this.imgData);
3960
-
3961
- // free memory
3962
3680
  this.image = null;
3963
3681
  return this.imgData = null;
3964
3682
  }
@@ -3971,7 +3689,6 @@ class PNGImage {
3971
3689
  const alphaChannel = Buffer.alloc(pixelCount);
3972
3690
  let i = p = a = 0;
3973
3691
  const len = pixels.length;
3974
- // For 16bit images copy only most significant byte (MSB) - PNG data is always stored in network byte order (MSB first)
3975
3692
  const skipByteCount = this.image.bits === 16 ? 1 : 0;
3976
3693
  while (i < len) {
3977
3694
  for (let colorIndex = 0; colorIndex < colorCount; colorIndex++) {
@@ -4006,10 +3723,6 @@ class PNGImage {
4006
3723
  }
4007
3724
  }
4008
3725
 
4009
- /*
4010
- PDFImage - embeds images in PDF documents
4011
- By Devon Govett
4012
- */
4013
3726
  class PDFImage {
4014
3727
  static open(src, label) {
4015
3728
  let data;
@@ -4041,17 +3754,16 @@ class PDFImage {
4041
3754
  var ImagesMixin = {
4042
3755
  initImages() {
4043
3756
  this._imageRegistry = {};
4044
- return this._imageCount = 0;
3757
+ this._imageCount = 0;
4045
3758
  },
4046
3759
  image(src, x, y, options = {}) {
4047
- let bh, bp, bw, image, ip, left, left1, rotateAngle, originX, originY;
3760
+ let bh, bp, bw, image, ip, left, left1, originX, originY;
4048
3761
  if (typeof x === 'object') {
4049
3762
  options = x;
4050
3763
  x = null;
4051
3764
  }
4052
-
4053
- // Ignore orientation based on document options or image options
4054
3765
  const ignoreOrientation = options.ignoreOrientation || options.ignoreOrientation !== false && this.options.ignoreOrientation;
3766
+ const inDocumentFlow = typeof y !== 'number';
4055
3767
  x = (left = x != null ? x : options.x) != null ? left : this.x;
4056
3768
  y = (left1 = y != null ? y : options.y) != null ? left1 : this.y;
4057
3769
  if (typeof src === 'string') {
@@ -4074,8 +3786,6 @@ var ImagesMixin = {
4074
3786
  width,
4075
3787
  height
4076
3788
  } = image;
4077
-
4078
- // If EXIF orientation calls for it, swap width and height
4079
3789
  if (!ignoreOrientation && image.orientation > 4) {
4080
3790
  [width, height] = [height, width];
4081
3791
  }
@@ -4127,80 +3837,70 @@ var ImagesMixin = {
4127
3837
  y = y + bh - h;
4128
3838
  }
4129
3839
  }
3840
+ let rotateAngle = 0;
3841
+ let xTransform = x;
3842
+ let yTransform = y;
3843
+ let hTransform = h;
3844
+ let wTransform = w;
4130
3845
  if (!ignoreOrientation) {
4131
3846
  switch (image.orientation) {
4132
- // No orientation (need to flip image, though, because of the default transform matrix on the document)
4133
3847
  default:
4134
3848
  case 1:
4135
- h = -h;
4136
- y -= h;
4137
- rotateAngle = 0;
3849
+ hTransform = -h;
3850
+ yTransform += h;
4138
3851
  break;
4139
- // Flip Horizontal
4140
3852
  case 2:
4141
- w = -w;
4142
- h = -h;
4143
- x -= w;
4144
- y -= h;
4145
- rotateAngle = 0;
3853
+ wTransform = -w;
3854
+ hTransform = -h;
3855
+ xTransform += w;
3856
+ yTransform += h;
4146
3857
  break;
4147
- // Rotate 180 degrees
4148
3858
  case 3:
4149
3859
  originX = x;
4150
3860
  originY = y;
4151
- h = -h;
4152
- x -= w;
3861
+ hTransform = -h;
3862
+ xTransform -= w;
4153
3863
  rotateAngle = 180;
4154
3864
  break;
4155
- // Flip vertical
4156
3865
  case 4:
4157
- // Do nothing, image will be flipped
4158
-
4159
3866
  break;
4160
- // Flip horizontally and rotate 270 degrees CW
4161
3867
  case 5:
4162
3868
  originX = x;
4163
3869
  originY = y;
4164
- [w, h] = [h, w];
4165
- y -= h;
3870
+ wTransform = h;
3871
+ hTransform = w;
3872
+ yTransform -= hTransform;
4166
3873
  rotateAngle = 90;
4167
3874
  break;
4168
- // Rotate 90 degrees CW
4169
3875
  case 6:
4170
3876
  originX = x;
4171
3877
  originY = y;
4172
- [w, h] = [h, w];
4173
- h = -h;
3878
+ wTransform = h;
3879
+ hTransform = -w;
4174
3880
  rotateAngle = 90;
4175
3881
  break;
4176
- // Flip horizontally and rotate 90 degrees CW
4177
3882
  case 7:
4178
3883
  originX = x;
4179
3884
  originY = y;
4180
- [w, h] = [h, w];
4181
- h = -h;
4182
- w = -w;
4183
- x -= w;
3885
+ hTransform = -w;
3886
+ wTransform = -h;
3887
+ xTransform += h;
4184
3888
  rotateAngle = 90;
4185
3889
  break;
4186
- // Rotate 270 degrees CW
4187
3890
  case 8:
4188
3891
  originX = x;
4189
3892
  originY = y;
4190
- [w, h] = [h, w];
4191
- h = -h;
4192
- x -= w;
4193
- y -= h;
3893
+ wTransform = h;
3894
+ hTransform = -w;
3895
+ xTransform -= h;
3896
+ yTransform += w;
4194
3897
  rotateAngle = -90;
4195
3898
  break;
4196
3899
  }
4197
3900
  } else {
4198
- h = -h;
4199
- y -= h;
4200
- rotateAngle = 0;
3901
+ hTransform = -h;
3902
+ yTransform += h;
4201
3903
  }
4202
-
4203
- // create link annotations if the link option is given
4204
3904
  if (options.link != null) {
4205
3905
  this.link(x, y, w, h, options.link);
4206
3906
  }
@@ -4210,9 +3910,7 @@ var ImagesMixin = {
4210
3910
  if (options.destination != null) {
4211
3911
  this.addNamedDestination(options.destination, 'XYZ', x, y, null);
4212
3912
  }
4213
-
4214
- // Set the current y position to below the image if it is in the document flow
4215
- if (this.y === y) {
3913
+ if (inDocumentFlow) {
4216
3914
  this.y += h;
4217
3915
  }
4218
3916
  this.save();
@@ -4221,7 +3919,7 @@ var ImagesMixin = {
4221
3919
  origin: [originX, originY]
4222
3920
  });
4223
3921
  }
4224
- this.transform(w, 0, 0, h, x, y);
3922
+ this.transform(wTransform, 0, 0, hTransform, xTransform, yTransform);
4225
3923
  this.addContent(`/${image.label} Do`);
4226
3924
  this.restore();
4227
3925
  return this;
@@ -4247,19 +3945,17 @@ var AnnotationsMixin = {
4247
3945
  options.Rect = this._convertRect(x, y, w, h);
4248
3946
  options.Border = [0, 0, 0];
4249
3947
  if (options.Subtype === 'Link' && typeof options.F === 'undefined') {
4250
- options.F = 1 << 2; // Print Annotation Flag
3948
+ options.F = 1 << 2;
4251
3949
  }
4252
3950
  if (options.Subtype !== 'Link') {
4253
3951
  if (options.C == null) {
4254
3952
  options.C = this._normalizeColor(options.color || [0, 0, 0]);
4255
3953
  }
4256
- } // convert colors
3954
+ }
4257
3955
  delete options.color;
4258
3956
  if (typeof options.Dest === 'string') {
4259
3957
  options.Dest = new String(options.Dest);
4260
3958
  }
4261
-
4262
- // Capitalize keys
4263
3959
  for (let key in options) {
4264
3960
  const val = options[key];
4265
3961
  options[key[0].toUpperCase() + key.slice(1)] = val;
@@ -4292,7 +3988,6 @@ var AnnotationsMixin = {
4292
3988
  link(x, y, w, h, url, options = {}) {
4293
3989
  options.Subtype = 'Link';
4294
3990
  if (typeof url === 'number') {
4295
- // Link to a page in the document (the page must already exist)
4296
3991
  const pages = this._root.data.Pages.data;
4297
3992
  if (url >= 0 && url < pages.Kids.length) {
4298
3993
  options.A = this.ref({
@@ -4304,7 +3999,6 @@ var AnnotationsMixin = {
4304
3999
  throw new Error(`The document has no page ${url}`);
4305
4000
  }
4306
4001
  } else {
4307
- // Link to an external url
4308
4002
  options.A = this.ref({
4309
4003
  S: 'URI',
4310
4004
  URI: new String(url)
@@ -4357,14 +4051,11 @@ var AnnotationsMixin = {
4357
4051
  return this.annotate(x, y, w, h, options);
4358
4052
  },
4359
4053
  fileAnnotation(x, y, w, h, file = {}, options = {}) {
4360
- // create hidden file
4361
4054
  const filespec = this.file(file.src, Object.assign({
4362
4055
  hidden: true
4363
4056
  }, file));
4364
4057
  options.Subtype = 'FileAttachment';
4365
4058
  options.FS = filespec;
4366
-
4367
- // add description from filespec unless description (Contents) has already been set
4368
4059
  if (options.Contents) {
4369
4060
  options.Contents = new String(options.Contents);
4370
4061
  } else if (filespec.data.Desc) {
@@ -4373,14 +4064,9 @@ var AnnotationsMixin = {
4373
4064
  return this.annotate(x, y, w, h, options);
4374
4065
  },
4375
4066
  _convertRect(x1, y1, w, h) {
4376
- // flip y1 and y2
4377
4067
  let y2 = y1;
4378
4068
  y1 += h;
4379
-
4380
- // make x2
4381
4069
  let x2 = x1 + w;
4382
-
4383
- // apply current transformation matrix to points
4384
4070
  const [m0, m1, m2, m3, m4, m5] = this._ctm;
4385
4071
  x1 = m0 * x1 + m2 * y1 + m4;
4386
4072
  y1 = m1 * x1 + m3 * y1 + m5;
@@ -4442,7 +4128,7 @@ class PDFOutline {
4442
4128
 
4443
4129
  var OutlineMixin = {
4444
4130
  initOutline() {
4445
- return this.outline = new PDFOutline(this, null, null, null);
4131
+ this.outline = new PDFOutline(this, null, null, null);
4446
4132
  },
4447
4133
  endOutline() {
4448
4134
  this.outline.endOutline();
@@ -4453,11 +4139,6 @@ var OutlineMixin = {
4453
4139
  }
4454
4140
  };
4455
4141
 
4456
- /*
4457
- PDFStructureContent - a reference to a marked structure content
4458
- By Ben Schmidt
4459
- */
4460
-
4461
4142
  class PDFStructureContent {
4462
4143
  constructor(pageRef, mcid) {
4463
4144
  this.refs = [{
@@ -4470,10 +4151,6 @@ class PDFStructureContent {
4470
4151
  }
4471
4152
  }
4472
4153
 
4473
- /*
4474
- PDFStructureElement - represents an element in the PDF logical structure tree
4475
- By Ben Schmidt
4476
- */
4477
4154
  class PDFStructureElement {
4478
4155
  constructor(document, type, options = {}, children = null) {
4479
4156
  this.document = document;
@@ -4481,7 +4158,6 @@ class PDFStructureElement {
4481
4158
  this._ended = false;
4482
4159
  this._flushed = false;
4483
4160
  this.dictionary = document.ref({
4484
- // Type: "StructElem",
4485
4161
  S: type
4486
4162
  });
4487
4163
  const data = this.dictionary.data;
@@ -4530,7 +4206,6 @@ class PDFStructureElement {
4530
4206
  this._addContentToParentTree(child);
4531
4207
  }
4532
4208
  if (typeof child === 'function' && this._attached) {
4533
- // _contentForClosure() adds the content to the parent tree
4534
4209
  child = this._contentForClosure(child);
4535
4210
  }
4536
4211
  this._children.push(child);
@@ -4606,10 +4281,6 @@ class PDFStructureElement {
4606
4281
  this.dictionary.data.K = [];
4607
4282
  this._children.forEach(child => this._flushChild(child));
4608
4283
  this.dictionary.end();
4609
-
4610
- // free memory used by children; the dictionary itself may still be
4611
- // referenced by a parent structure element or root, but we can
4612
- // at least trim the tree here
4613
4284
  this._children = [];
4614
4285
  this.dictionary.data.K = null;
4615
4286
  this._flushed = true;
@@ -4630,7 +4301,7 @@ class PDFStructureElement {
4630
4301
  this.dictionary.data.K.push(mcid);
4631
4302
  } else {
4632
4303
  this.dictionary.data.K.push({
4633
- Type: "MCR",
4304
+ Type: 'MCR',
4634
4305
  Pg: pageRef,
4635
4306
  MCID: mcid
4636
4307
  });
@@ -4640,25 +4311,18 @@ class PDFStructureElement {
4640
4311
  }
4641
4312
  }
4642
4313
 
4643
- /*
4644
- PDFNumberTree - represents a number tree object
4645
- */
4646
4314
  class PDFNumberTree extends PDFTree {
4647
4315
  _compareKeys(a, b) {
4648
4316
  return parseInt(a) - parseInt(b);
4649
4317
  }
4650
4318
  _keysName() {
4651
- return "Nums";
4319
+ return 'Nums';
4652
4320
  }
4653
4321
  _dataForKey(k) {
4654
4322
  return parseInt(k);
4655
4323
  }
4656
4324
  }
4657
4325
 
4658
- /*
4659
- Markings mixin - support marked content sequences in content streams
4660
- By Ben Schmidt
4661
- */
4662
4326
  var MarkingsMixin = {
4663
4327
  initMarkings(options) {
4664
4328
  this.structChildren = [];
@@ -4794,7 +4458,6 @@ var MarkingsMixin = {
4794
4458
  return this.getStructTreeRoot().data.ParentTree;
4795
4459
  },
4796
4460
  createStructParentTreeNextKey() {
4797
- // initialise the MarkInfo dictionary
4798
4461
  this.getMarkInfoDictionary();
4799
4462
  const structTreeRoot = this.getStructTreeRoot();
4800
4463
  const key = structTreeRoot.data.ParentTreeNextKey++;
@@ -4858,10 +4521,6 @@ const FORMAT_DEFAULT = {
4858
4521
  }
4859
4522
  };
4860
4523
  var AcroFormMixin = {
4861
- /**
4862
- * Must call if adding AcroForms to a document. Must also call font() before
4863
- * this method to set the default font.
4864
- */
4865
4524
  initForm() {
4866
4525
  if (!this._font) {
4867
4526
  throw new Error('Must set a font before calling initForm method');
@@ -4884,9 +4543,6 @@ var AcroFormMixin = {
4884
4543
  this._root.data.AcroForm = AcroForm;
4885
4544
  return this;
4886
4545
  },
4887
- /**
4888
- * Called automatically by document.js
4889
- */
4890
4546
  endAcroForm() {
4891
4547
  if (this._root.data.AcroForm) {
4892
4548
  if (!Object.keys(this._acroform.fonts).length && !this._acroform.defaultFont) {
@@ -4912,38 +4568,18 @@ var AcroFormMixin = {
4912
4568
  }
4913
4569
  return this;
4914
4570
  },
4915
- /**
4916
- * Creates and adds a form field to the document. Form fields are intermediate
4917
- * nodes in a PDF form that are used to specify form name heirarchy and form
4918
- * value defaults.
4919
- * @param {string} name - field name (T attribute in field dictionary)
4920
- * @param {object} options - other attributes to include in field dictionary
4921
- */
4922
4571
  formField(name, options = {}) {
4923
4572
  let fieldDict = this._fieldDict(name, null, options);
4924
4573
  let fieldRef = this.ref(fieldDict);
4925
4574
  this._addToParent(fieldRef);
4926
4575
  return fieldRef;
4927
4576
  },
4928
- /**
4929
- * Creates and adds a Form Annotation to the document. Form annotations are
4930
- * called Widget annotations internally within a PDF file.
4931
- * @param {string} name - form field name (T attribute of widget annotation
4932
- * dictionary)
4933
- * @param {number} x
4934
- * @param {number} y
4935
- * @param {number} w
4936
- * @param {number} h
4937
- * @param {object} options
4938
- */
4939
4577
  formAnnotation(name, type, x, y, w, h, options = {}) {
4940
4578
  let fieldDict = this._fieldDict(name, type, options);
4941
4579
  fieldDict.Subtype = 'Widget';
4942
4580
  if (fieldDict.F === undefined) {
4943
- fieldDict.F = 4; // print the annotation
4581
+ fieldDict.F = 4;
4944
4582
  }
4945
-
4946
- // Add Field annot to page, and get it's ref
4947
4583
  this.annotate(x, y, w, h, fieldDict);
4948
4584
  let annotRef = this.page.annotations[this.page.annotations.length - 1];
4949
4585
  return this._addToParent(annotRef);
@@ -5104,23 +4740,18 @@ var AcroFormMixin = {
5104
4740
  delete options.align;
5105
4741
  }
5106
4742
  if (result !== 0) {
5107
- options.Q = result; // default
4743
+ options.Q = result;
5108
4744
  }
5109
4745
  return options;
5110
4746
  },
5111
4747
  _resolveFont(options) {
5112
- // add current font to document-level AcroForm dict if necessary
5113
4748
  if (this._acroform.fonts[this._font.id] == null) {
5114
4749
  this._acroform.fonts[this._font.id] = this._font.ref();
5115
4750
  }
5116
-
5117
- // add current font to field's resource dict (RD) if not the default acroform font
5118
4751
  if (this._acroform.defaultFont !== this._font.name) {
5119
4752
  options.DR = {
5120
4753
  Font: {}
5121
4754
  };
5122
-
5123
- // Get the fontSize option. If not set use auto sizing
5124
4755
  const fontSize = options.fontSize || 0;
5125
4756
  options.DR.Font[this._font.id] = this._font.ref();
5126
4757
  options.DA = new String(`/${this._font.id} ${fontSize} Tf 0 g`);
@@ -5172,19 +4803,6 @@ var AcroFormMixin = {
5172
4803
  };
5173
4804
 
5174
4805
  var AttachmentsMixin = {
5175
- /**
5176
- * Embed contents of `src` in PDF
5177
- * @param {Buffer | ArrayBuffer | string} src input Buffer, ArrayBuffer, base64 encoded string or path to file
5178
- * @param {object} options
5179
- * * options.name: filename to be shown in PDF, will use `src` if none set
5180
- * * options.type: filetype to be shown in PDF
5181
- * * options.description: description to be shown in PDF
5182
- * * options.hidden: if true, do not add attachment to EmbeddedFiles dictionary. Useful for file attachment annotations
5183
- * * options.creationDate: override creation date
5184
- * * options.modifiedDate: override modified date
5185
- * * options.relationship: Relationship between the PDF document and its attached file. Can be 'Alternative', 'Data', 'Source', 'Supplement' or 'Unspecified'.
5186
- * @returns filespec reference
5187
- */
5188
4806
  file(src, options = {}) {
5189
4807
  options.name = options.name || src;
5190
4808
  options.relationship = options.relationship || 'Unspecified';
@@ -5212,8 +4830,6 @@ var AttachmentsMixin = {
5212
4830
  if (!data) {
5213
4831
  throw new Error(`Could not read contents of file at filepath ${src}`);
5214
4832
  }
5215
-
5216
- // update CreationDate and ModDate
5217
4833
  const {
5218
4834
  birthtime,
5219
4835
  ctime
@@ -5222,26 +4838,18 @@ var AttachmentsMixin = {
5222
4838
  refBody.Params.ModDate = ctime;
5223
4839
  }
5224
4840
  }
5225
-
5226
- // override creation date and modified date
5227
4841
  if (options.creationDate instanceof Date) {
5228
4842
  refBody.Params.CreationDate = options.creationDate;
5229
4843
  }
5230
4844
  if (options.modifiedDate instanceof Date) {
5231
4845
  refBody.Params.ModDate = options.modifiedDate;
5232
4846
  }
5233
- // add optional subtype
5234
4847
  if (options.type) {
5235
4848
  refBody.Subtype = options.type.replace('/', '#2F');
5236
4849
  }
5237
-
5238
- // add checksum and size information
5239
4850
  const checksum = CryptoJS.MD5(CryptoJS.lib.WordArray.create(new Uint8Array(data)));
5240
4851
  refBody.Params.CheckSum = new String(checksum);
5241
4852
  refBody.Params.Size = data.byteLength;
5242
-
5243
- // save some space when embedding the same file again
5244
- // if a file with the same name and metadata exists, reuse its reference
5245
4853
  let ref;
5246
4854
  if (!this._fileRegistry) this._fileRegistry = {};
5247
4855
  let file = this._fileRegistry[options.name];
@@ -5255,7 +4863,6 @@ var AttachmentsMixin = {
5255
4863
  ref
5256
4864
  };
5257
4865
  }
5258
- // add filespec for embedded file
5259
4866
  const fileSpecBody = {
5260
4867
  Type: 'Filespec',
5261
4868
  AFRelationship: options.relationship,
@@ -5273,8 +4880,6 @@ var AttachmentsMixin = {
5273
4880
  if (!options.hidden) {
5274
4881
  this.addNamedEmbeddedFile(options.name, filespec);
5275
4882
  }
5276
-
5277
- // Add file to the catalogue to be PDF/A3 compliant
5278
4883
  if (this._root.data.AF) {
5279
4884
  this._root.data.AF.push(filespec);
5280
4885
  } else {
@@ -5283,8 +4888,6 @@ var AttachmentsMixin = {
5283
4888
  return filespec;
5284
4889
  }
5285
4890
  };
5286
-
5287
- /** check two embedded file metadata objects for equality */
5288
4891
  function isEqual(a, b) {
5289
4892
  return a.Subtype === b.Subtype && a.Params.CheckSum.toString() === b.Params.CheckSum.toString() && a.Params.Size === b.Params.Size && a.Params.CreationDate.getTime() === b.Params.CreationDate.getTime() && (a.Params.ModDate === undefined && b.Params.ModDate === undefined || a.Params.ModDate.getTime() === b.Params.ModDate.getTime());
5290
4893
  }
@@ -5295,7 +4898,6 @@ var PDFA = {
5295
4898
  this.subset_conformance = pSubset.charAt(pSubset.length - 1).toUpperCase();
5296
4899
  this.subset = parseInt(pSubset.charAt(pSubset.length - 2));
5297
4900
  } else {
5298
- // Default to Basic conformance when user doesn't specify
5299
4901
  this.subset_conformance = 'B';
5300
4902
  this.subset = parseInt(pSubset.charAt(pSubset.length - 1));
5301
4903
  }
@@ -5380,6 +4982,642 @@ var SubsetMixin = {
5380
4982
  }
5381
4983
  };
5382
4984
 
4985
+ const ROW_FIELDS = ['height', 'minHeight', 'maxHeight'];
4986
+ const COLUMN_FIELDS = ['width', 'minWidth', 'maxWidth'];
4987
+ function memoize(fn, maxSize) {
4988
+ const cache = new Map();
4989
+ return function (...args) {
4990
+ const key = args[0];
4991
+ if (!cache.has(key)) {
4992
+ cache.set(key, fn(...args));
4993
+ if (cache.size > maxSize) cache.delete(cache.keys().next());
4994
+ }
4995
+ return cache.get(key);
4996
+ };
4997
+ }
4998
+ function isObject(item) {
4999
+ return item && typeof item === 'object' && !Array.isArray(item);
5000
+ }
5001
+ function deepMerge(target, ...sources) {
5002
+ if (!isObject(target)) return target;
5003
+ target = deepClone(target);
5004
+ for (const source of sources) {
5005
+ if (isObject(source)) {
5006
+ for (const key in source) {
5007
+ if (isObject(source[key])) {
5008
+ if (!(key in target)) target[key] = {};
5009
+ target[key] = deepMerge(target[key], source[key]);
5010
+ } else if (source[key] !== undefined) {
5011
+ target[key] = deepClone(source[key]);
5012
+ }
5013
+ }
5014
+ }
5015
+ }
5016
+ return target;
5017
+ }
5018
+ function deepClone(obj) {
5019
+ let result = obj;
5020
+ if (typeof obj == 'object') {
5021
+ result = Array.isArray(obj) ? [] : {};
5022
+ for (const key in obj) result[key] = deepClone(obj[key]);
5023
+ }
5024
+ return result;
5025
+ }
5026
+
5027
+ function normalizedDefaultStyle(defaultStyleInternal) {
5028
+ let defaultStyle = defaultStyleInternal;
5029
+ if (typeof defaultStyle !== 'object') defaultStyle = {
5030
+ text: defaultStyle
5031
+ };
5032
+ const defaultRowStyle = Object.fromEntries(Object.entries(defaultStyle).filter(([k]) => ROW_FIELDS.includes(k)));
5033
+ const defaultColStyle = Object.fromEntries(Object.entries(defaultStyle).filter(([k]) => COLUMN_FIELDS.includes(k)));
5034
+ defaultStyle.padding = normalizeSides(defaultStyle.padding);
5035
+ defaultStyle.border = normalizeSides(defaultStyle.border);
5036
+ defaultStyle.borderColor = normalizeSides(defaultStyle.borderColor);
5037
+ defaultStyle.align = normalizeAlignment(defaultStyle.align);
5038
+ return {
5039
+ defaultStyle,
5040
+ defaultRowStyle,
5041
+ defaultColStyle
5042
+ };
5043
+ }
5044
+ function normalizedRowStyle(defaultRowStyle, rowStyleInternal, i) {
5045
+ let rowStyle = rowStyleInternal(i);
5046
+ if (rowStyle == null || typeof rowStyle !== 'object') {
5047
+ rowStyle = {
5048
+ height: rowStyle
5049
+ };
5050
+ }
5051
+ rowStyle.padding = normalizeSides(rowStyle.padding);
5052
+ rowStyle.border = normalizeSides(rowStyle.border);
5053
+ rowStyle.borderColor = normalizeSides(rowStyle.borderColor);
5054
+ rowStyle.align = normalizeAlignment(rowStyle.align);
5055
+ rowStyle = deepMerge(defaultRowStyle, rowStyle);
5056
+ const document = this.document;
5057
+ const page = document.page;
5058
+ const contentHeight = page.contentHeight;
5059
+ if (rowStyle.height == null || rowStyle.height === 'auto') {
5060
+ rowStyle.height = 'auto';
5061
+ } else {
5062
+ rowStyle.height = document.sizeToPoint(rowStyle.height, 0, page, contentHeight);
5063
+ }
5064
+ rowStyle.minHeight = document.sizeToPoint(rowStyle.minHeight, 0, page, contentHeight);
5065
+ rowStyle.maxHeight = document.sizeToPoint(rowStyle.maxHeight, 0, page, contentHeight);
5066
+ return rowStyle;
5067
+ }
5068
+ function normalizedColumnStyle(defaultColStyle, colStyleInternal, i) {
5069
+ let colStyle = colStyleInternal(i);
5070
+ if (colStyle == null || typeof colStyle !== 'object') {
5071
+ colStyle = {
5072
+ width: colStyle
5073
+ };
5074
+ }
5075
+ colStyle.padding = normalizeSides(colStyle.padding);
5076
+ colStyle.border = normalizeSides(colStyle.border);
5077
+ colStyle.borderColor = normalizeSides(colStyle.borderColor);
5078
+ colStyle.align = normalizeAlignment(colStyle.align);
5079
+ colStyle = deepMerge(defaultColStyle, colStyle);
5080
+ if (colStyle.width == null || colStyle.width === '*') {
5081
+ colStyle.width = '*';
5082
+ } else {
5083
+ colStyle.width = this.document.sizeToPoint(colStyle.width, 0, this.document.page, this._maxWidth);
5084
+ }
5085
+ colStyle.minWidth = this.document.sizeToPoint(colStyle.minWidth, 0, this.document.page, this._maxWidth);
5086
+ colStyle.maxWidth = this.document.sizeToPoint(colStyle.maxWidth, 0, this.document.page, this._maxWidth);
5087
+ return colStyle;
5088
+ }
5089
+ function normalizeAlignment(align) {
5090
+ return align == null || typeof align === 'string' ? {
5091
+ x: align,
5092
+ y: align
5093
+ } : align;
5094
+ }
5095
+
5096
+ function normalizeTable() {
5097
+ const doc = this.document;
5098
+ const opts = this.opts;
5099
+ let index = doc._tableIndex++;
5100
+ this._id = new String(opts.id ?? `table-${index}`);
5101
+ this._position = {
5102
+ x: doc.sizeToPoint(opts.position?.x, doc.x),
5103
+ y: doc.sizeToPoint(opts.position?.y, doc.y)
5104
+ };
5105
+ this._maxWidth = doc.sizeToPoint(opts.maxWidth, doc.page.width - doc.page.margins.right - this._position.x);
5106
+ const {
5107
+ defaultStyle,
5108
+ defaultColStyle,
5109
+ defaultRowStyle
5110
+ } = normalizedDefaultStyle(opts.defaultStyle);
5111
+ this._defaultStyle = defaultStyle;
5112
+ let colStyle;
5113
+ if (opts.columnStyles) {
5114
+ if (Array.isArray(opts.columnStyles)) {
5115
+ colStyle = i => opts.columnStyles[i];
5116
+ } else if (typeof opts.columnStyles === 'function') {
5117
+ colStyle = memoize(i => opts.columnStyles(i), Infinity);
5118
+ } else if (typeof opts.columnStyles === 'object') {
5119
+ colStyle = () => opts.columnStyles;
5120
+ }
5121
+ }
5122
+ if (!colStyle) colStyle = () => ({});
5123
+ this._colStyle = normalizedColumnStyle.bind(this, defaultColStyle, colStyle);
5124
+ let rowStyle;
5125
+ if (opts.rowStyles) {
5126
+ if (Array.isArray(opts.rowStyles)) {
5127
+ rowStyle = i => opts.rowStyles[i];
5128
+ } else if (typeof opts.rowStyles === 'function') {
5129
+ rowStyle = memoize(i => opts.rowStyles(i), 10);
5130
+ } else if (typeof opts.rowStyles === 'object') {
5131
+ rowStyle = () => opts.rowStyles;
5132
+ }
5133
+ }
5134
+ if (!rowStyle) rowStyle = () => ({});
5135
+ this._rowStyle = normalizedRowStyle.bind(this, defaultRowStyle, rowStyle);
5136
+ }
5137
+ function normalizeText(text) {
5138
+ if (text != null) text = `${text}`;
5139
+ return text;
5140
+ }
5141
+ function normalizeCell(cell, rowIndex, colIndex) {
5142
+ const colStyle = this._colStyle(colIndex);
5143
+ let rowStyle = this._rowStyle(rowIndex);
5144
+ const font = deepMerge({}, colStyle.font, rowStyle.font, cell.font);
5145
+ const customFont = Object.values(font).filter(v => v != null).length > 0;
5146
+ const doc = this.document;
5147
+ const rollbackFont = doc._fontSource;
5148
+ const rollbackFontSize = doc._fontSize;
5149
+ const rollbackFontFamily = doc._fontFamily;
5150
+ if (customFont) {
5151
+ if (font.src) doc.font(font.src, font.family);
5152
+ if (font.size) doc.fontSize(font.size);
5153
+ rowStyle = this._rowStyle(rowIndex);
5154
+ }
5155
+ cell.padding = normalizeSides(cell.padding);
5156
+ cell.border = normalizeSides(cell.border);
5157
+ cell.borderColor = normalizeSides(cell.borderColor);
5158
+ const config = deepMerge(this._defaultStyle, colStyle, rowStyle, cell);
5159
+ config.rowIndex = rowIndex;
5160
+ config.colIndex = colIndex;
5161
+ config.font = font ?? {};
5162
+ config.customFont = customFont;
5163
+ config.text = normalizeText(config.text);
5164
+ config.rowSpan = config.rowSpan ?? 1;
5165
+ config.colSpan = config.colSpan ?? 1;
5166
+ config.padding = normalizeSides(config.padding, '0.25em', x => doc.sizeToPoint(x, '0.25em'));
5167
+ config.border = normalizeSides(config.border, 1, x => doc.sizeToPoint(x, 1));
5168
+ config.borderColor = normalizeSides(config.borderColor, 'black', x => x ?? 'black');
5169
+ config.align = normalizeAlignment(config.align);
5170
+ config.align.x = config.align.x ?? 'left';
5171
+ config.align.y = config.align.y ?? 'top';
5172
+ config.textStroke = doc.sizeToPoint(config.textStroke, 0);
5173
+ config.textStrokeColor = config.textStrokeColor ?? 'black';
5174
+ config.textColor = config.textColor ?? 'black';
5175
+ config.textOptions = config.textOptions ?? {};
5176
+ config.id = new String(config.id ?? `${this._id}-${rowIndex}-${colIndex}`);
5177
+ config.type = config.type?.toUpperCase() === 'TH' ? 'TH' : 'TD';
5178
+ if (config.scope) {
5179
+ config.scope = config.scope.toLowerCase();
5180
+ if (config.scope === 'row') config.scope = 'Row';else if (config.scope === 'both') config.scope = 'Both';else if (config.scope === 'column') config.scope = 'Column';
5181
+ }
5182
+ if (typeof this.opts.debug === 'boolean') config.debug = this.opts.debug;
5183
+ if (customFont) doc.font(rollbackFont, rollbackFontFamily, rollbackFontSize);
5184
+ return config;
5185
+ }
5186
+ function normalizeRow(row, rowIndex) {
5187
+ if (!this._cellClaim) this._cellClaim = new Set();
5188
+ let colIndex = 0;
5189
+ return row.map(cell => {
5190
+ if (cell == null || typeof cell !== 'object') cell = {
5191
+ text: cell
5192
+ };
5193
+ while (this._cellClaim.has(`${rowIndex},${colIndex}`)) {
5194
+ colIndex++;
5195
+ }
5196
+ cell = normalizeCell.call(this, cell, rowIndex, colIndex);
5197
+ for (let i = 0; i < cell.rowSpan; i++) {
5198
+ for (let j = 0; j < cell.colSpan; j++) {
5199
+ this._cellClaim.add(`${rowIndex + i},${colIndex + j}`);
5200
+ }
5201
+ }
5202
+ colIndex += cell.colSpan;
5203
+ return cell;
5204
+ });
5205
+ }
5206
+
5207
+ function ensure(row) {
5208
+ this._columnWidths = [];
5209
+ ensureColumnWidths.call(this, row.reduce((a, cell) => a + cell.colSpan, 0));
5210
+ this._rowHeights = [];
5211
+ this._rowYPos = [this._position.y];
5212
+ this._rowBuffer = new Set();
5213
+ }
5214
+ function ensureColumnWidths(numCols) {
5215
+ let starColumnIndexes = [];
5216
+ let starMinAcc = 0;
5217
+ let unclaimedWidth = this._maxWidth;
5218
+ for (let i = 0; i < numCols; i++) {
5219
+ let col = this._colStyle(i);
5220
+ if (col.width === '*') {
5221
+ starColumnIndexes[i] = col;
5222
+ starMinAcc += col.minWidth;
5223
+ } else {
5224
+ unclaimedWidth -= col.width;
5225
+ this._columnWidths[i] = col.width;
5226
+ }
5227
+ }
5228
+ let starColCount = starColumnIndexes.reduce(x => x + 1, 0);
5229
+ if (starMinAcc >= unclaimedWidth) {
5230
+ starColumnIndexes.forEach((cell, i) => {
5231
+ this._columnWidths[i] = cell.minWidth;
5232
+ });
5233
+ } else if (starColCount > 0) {
5234
+ starColumnIndexes.forEach((col, i) => {
5235
+ let starSize = unclaimedWidth / starColCount;
5236
+ this._columnWidths[i] = Math.max(starSize, col.minWidth);
5237
+ if (col.maxWidth > 0) {
5238
+ this._columnWidths[i] = Math.min(this._columnWidths[i], col.maxWidth);
5239
+ }
5240
+ unclaimedWidth -= this._columnWidths[i];
5241
+ starColCount--;
5242
+ });
5243
+ }
5244
+ let tempX = this._position.x;
5245
+ this._columnXPos = Array.from(this._columnWidths, v => {
5246
+ const t = tempX;
5247
+ tempX += v;
5248
+ return t;
5249
+ });
5250
+ }
5251
+ function measure(row, rowIndex) {
5252
+ row.forEach(cell => this._rowBuffer.add(cell));
5253
+ if (rowIndex > 0) {
5254
+ this._rowYPos[rowIndex] = this._rowYPos[rowIndex - 1] + this._rowHeights[rowIndex - 1];
5255
+ }
5256
+ const rowStyle = this._rowStyle(rowIndex);
5257
+ let toRender = [];
5258
+ this._rowBuffer.forEach(cell => {
5259
+ if (cell.rowIndex + cell.rowSpan - 1 === rowIndex) {
5260
+ toRender.push(measureCell.call(this, cell, rowStyle.height));
5261
+ this._rowBuffer.delete(cell);
5262
+ }
5263
+ });
5264
+ let rowHeight = rowStyle.height;
5265
+ if (rowHeight === 'auto') {
5266
+ rowHeight = toRender.reduce((acc, cell) => {
5267
+ let minHeight = cell.textBounds.height + cell.padding.top + cell.padding.bottom;
5268
+ for (let i = 0; i < cell.rowSpan - 1; i++) {
5269
+ minHeight -= this._rowHeights[cell.rowIndex + i];
5270
+ }
5271
+ return Math.max(acc, minHeight);
5272
+ }, 0);
5273
+ }
5274
+ rowHeight = Math.max(rowHeight, rowStyle.minHeight);
5275
+ if (rowStyle.maxHeight > 0) {
5276
+ rowHeight = Math.min(rowHeight, rowStyle.maxHeight);
5277
+ }
5278
+ this._rowHeights[rowIndex] = rowHeight;
5279
+ let newPage = false;
5280
+ if (rowHeight > this.document.page.contentHeight) {
5281
+ console.warn(new Error(`Row ${rowIndex} requested more than the safe page height, row has been clamped`).stack.slice(7));
5282
+ this._rowHeights[rowIndex] = this.document.page.maxY() - this._rowYPos[rowIndex];
5283
+ } else if (this._rowYPos[rowIndex] + rowHeight >= this.document.page.maxY()) {
5284
+ this._rowYPos[rowIndex] = this.document.page.margins.top;
5285
+ newPage = true;
5286
+ }
5287
+ return {
5288
+ newPage,
5289
+ toRender: toRender.map(cell => measureCell.call(this, cell, rowHeight))
5290
+ };
5291
+ }
5292
+ function measureCell(cell, rowHeight) {
5293
+ let cellWidth = 0;
5294
+ for (let i = 0; i < cell.colSpan; i++) {
5295
+ cellWidth += this._columnWidths[cell.colIndex + i];
5296
+ }
5297
+ let cellHeight = rowHeight;
5298
+ if (cellHeight === 'auto') {
5299
+ cellHeight = this.document.page.contentHeight;
5300
+ } else {
5301
+ for (let i = 0; i < cell.rowSpan - 1; i++) {
5302
+ cellHeight += this._rowHeights[cell.rowIndex + i];
5303
+ }
5304
+ }
5305
+ const textAllocatedWidth = cellWidth - cell.padding.left - cell.padding.right;
5306
+ const textAllocatedHeight = cellHeight - cell.padding.top - cell.padding.bottom;
5307
+ const rotation = cell.textOptions.rotation ?? 0;
5308
+ const {
5309
+ width: textMaxWidth,
5310
+ height: textMaxHeight
5311
+ } = computeBounds(rotation, textAllocatedWidth, textAllocatedHeight);
5312
+ const textOptions = {
5313
+ align: cell.align.x,
5314
+ ellipsis: true,
5315
+ stroke: cell.textStroke > 0,
5316
+ fill: true,
5317
+ width: textMaxWidth,
5318
+ height: textMaxHeight,
5319
+ rotation,
5320
+ ...cell.textOptions
5321
+ };
5322
+ let textBounds = {
5323
+ x: 0,
5324
+ y: 0,
5325
+ width: 0,
5326
+ height: 0
5327
+ };
5328
+ if (cell.text) {
5329
+ const rollbackFont = this.document._fontSource;
5330
+ const rollbackFontSize = this.document._fontSize;
5331
+ const rollbackFontFamily = this.document._fontFamily;
5332
+ if (cell.font?.src) this.document.font(cell.font.src, cell.font?.family);
5333
+ if (cell.font?.size) this.document.fontSize(cell.font.size);
5334
+ const unRotatedTextBounds = this.document.boundsOfString(cell.text, 0, 0, {
5335
+ ...textOptions,
5336
+ rotation: 0
5337
+ });
5338
+ textOptions.width = unRotatedTextBounds.width;
5339
+ textOptions.height = unRotatedTextBounds.height;
5340
+ textBounds = this.document.boundsOfString(cell.text, 0, 0, textOptions);
5341
+ this.document.font(rollbackFont, rollbackFontFamily, rollbackFontSize);
5342
+ }
5343
+ return {
5344
+ ...cell,
5345
+ textOptions,
5346
+ x: this._columnXPos[cell.colIndex],
5347
+ y: this._rowYPos[cell.rowIndex],
5348
+ textX: this._columnXPos[cell.colIndex] + cell.padding.left,
5349
+ textY: this._rowYPos[cell.rowIndex] + cell.padding.top,
5350
+ width: cellWidth,
5351
+ height: cellHeight,
5352
+ textAllocatedHeight,
5353
+ textAllocatedWidth,
5354
+ textBounds
5355
+ };
5356
+ }
5357
+ function computeBounds(rotation, allocWidth, allocHeight) {
5358
+ let textMaxWidth, textMaxHeight;
5359
+ const cos = cosine(rotation);
5360
+ const sin = sine(rotation);
5361
+ if (rotation === 0 || rotation === 180) {
5362
+ textMaxWidth = allocWidth;
5363
+ textMaxHeight = allocHeight;
5364
+ } else if (rotation === 90 || rotation === 270) {
5365
+ textMaxWidth = allocHeight;
5366
+ textMaxHeight = allocWidth;
5367
+ } else if (rotation < 90 || rotation > 180 && rotation < 270) {
5368
+ textMaxWidth = allocWidth / (2 * cos);
5369
+ textMaxHeight = allocWidth / (2 * sin);
5370
+ } else {
5371
+ textMaxHeight = allocWidth / (2 * cos);
5372
+ textMaxWidth = allocWidth / (2 * sin);
5373
+ }
5374
+ const EF = sin * textMaxWidth;
5375
+ const FG = cos * textMaxHeight;
5376
+ if (EF + FG > allocHeight) {
5377
+ const denominator = cos * cos - sin * sin;
5378
+ if (rotation === 0 || rotation === 180) {
5379
+ textMaxWidth = allocWidth;
5380
+ textMaxHeight = allocHeight;
5381
+ } else if (rotation === 90 || rotation === 270) {
5382
+ textMaxWidth = allocHeight;
5383
+ textMaxHeight = allocWidth;
5384
+ } else if (rotation < 90 || rotation > 180 && rotation < 270) {
5385
+ textMaxWidth = (allocWidth * cos - allocHeight * sin) / denominator;
5386
+ textMaxHeight = (allocHeight * cos - allocWidth * sin) / denominator;
5387
+ } else {
5388
+ textMaxHeight = (allocWidth * cos - allocHeight * sin) / denominator;
5389
+ textMaxWidth = (allocHeight * cos - allocWidth * sin) / denominator;
5390
+ }
5391
+ }
5392
+ return {
5393
+ width: Math.abs(textMaxWidth),
5394
+ height: Math.abs(textMaxHeight)
5395
+ };
5396
+ }
5397
+
5398
+ function accommodateTable() {
5399
+ const structParent = this.opts.structParent;
5400
+ if (structParent) {
5401
+ this._tableStruct = this.document.struct('Table');
5402
+ this._tableStruct.dictionary.data.ID = this._id;
5403
+ if (structParent instanceof PDFStructureElement) {
5404
+ structParent.add(this._tableStruct);
5405
+ } else if (structParent instanceof PDFDocument) {
5406
+ structParent.addStructure(this._tableStruct);
5407
+ }
5408
+ this._headerRowLookup = {};
5409
+ this._headerColumnLookup = {};
5410
+ }
5411
+ }
5412
+ function accommodateCleanup() {
5413
+ if (this._tableStruct) this._tableStruct.end();
5414
+ }
5415
+ function accessibleRow(row, rowIndex, renderCell) {
5416
+ const rowStruct = this.document.struct('TR');
5417
+ rowStruct.dictionary.data.ID = new String(`${this._id}-${rowIndex}`);
5418
+ this._tableStruct.add(rowStruct);
5419
+ row.forEach(cell => renderCell(cell, rowStruct));
5420
+ rowStruct.end();
5421
+ }
5422
+ function accessibleCell(cell, rowStruct, callback) {
5423
+ const doc = this.document;
5424
+ const cellStruct = doc.struct(cell.type, {
5425
+ title: cell.title
5426
+ });
5427
+ cellStruct.dictionary.data.ID = cell.id;
5428
+ rowStruct.add(cellStruct);
5429
+ const padding = cell.padding;
5430
+ const border = cell.border;
5431
+ const attributes = {
5432
+ O: 'Table',
5433
+ Width: cell.width,
5434
+ Height: cell.height,
5435
+ Padding: [padding.top, padding.bottom, padding.left, padding.right],
5436
+ RowSpan: cell.rowSpan > 1 ? cell.rowSpan : undefined,
5437
+ ColSpan: cell.colSpan > 1 ? cell.colSpan : undefined,
5438
+ BorderThickness: [border.top, border.bottom, border.left, border.right]
5439
+ };
5440
+ if (cell.type === 'TH') {
5441
+ if (cell.scope === 'Row' || cell.scope === 'Both') {
5442
+ for (let i = 0; i < cell.rowSpan; i++) {
5443
+ if (!this._headerRowLookup[cell.rowIndex + i]) {
5444
+ this._headerRowLookup[cell.rowIndex + i] = [];
5445
+ }
5446
+ this._headerRowLookup[cell.rowIndex + i].push(cell.id);
5447
+ }
5448
+ attributes.Scope = cell.scope;
5449
+ }
5450
+ if (cell.scope === 'Column' || cell.scope === 'Both') {
5451
+ for (let i = 0; i < cell.colSpan; i++) {
5452
+ if (!this._headerColumnLookup[cell.colIndex + i]) {
5453
+ this._headerColumnLookup[cell.colIndex + i] = [];
5454
+ }
5455
+ this._headerColumnLookup[cell.colIndex + i].push(cell.id);
5456
+ }
5457
+ attributes.Scope = cell.scope;
5458
+ }
5459
+ }
5460
+ const Headers = new Set([...Array.from({
5461
+ length: cell.colSpan
5462
+ }, (_, i) => this._headerColumnLookup[cell.colIndex + i]).flat(), ...Array.from({
5463
+ length: cell.rowSpan
5464
+ }, (_, i) => this._headerRowLookup[cell.rowIndex + i]).flat()].filter(Boolean));
5465
+ if (Headers.size) attributes.Headers = Array.from(Headers);
5466
+ const normalizeColor = doc._normalizeColor;
5467
+ if (cell.backgroundColor != null) {
5468
+ attributes.BackgroundColor = normalizeColor(cell.backgroundColor);
5469
+ }
5470
+ const hasBorder = [border.top, border.bottom, border.left, border.right];
5471
+ if (hasBorder.some(x => x)) {
5472
+ const borderColor = cell.borderColor;
5473
+ attributes.BorderColor = [hasBorder[0] ? normalizeColor(borderColor.top) : null, hasBorder[1] ? normalizeColor(borderColor.bottom) : null, hasBorder[2] ? normalizeColor(borderColor.left) : null, hasBorder[3] ? normalizeColor(borderColor.right) : null];
5474
+ }
5475
+ Object.keys(attributes).forEach(key => attributes[key] === undefined && delete attributes[key]);
5476
+ cellStruct.dictionary.data.A = doc.ref(attributes);
5477
+ cellStruct.add(callback);
5478
+ cellStruct.end();
5479
+ cellStruct.dictionary.data.A.end();
5480
+ }
5481
+
5482
+ function renderRow(row, rowIndex) {
5483
+ if (this._tableStruct) {
5484
+ accessibleRow.call(this, row, rowIndex, renderCell.bind(this));
5485
+ } else {
5486
+ row.forEach(cell => renderCell.call(this, cell));
5487
+ }
5488
+ return this._rowYPos[rowIndex] + this._rowHeights[rowIndex];
5489
+ }
5490
+ function renderCell(cell, rowStruct) {
5491
+ const cellRenderer = () => {
5492
+ if (cell.backgroundColor != null) {
5493
+ this.document.save().rect(cell.x, cell.y, cell.width, cell.height).fill(cell.backgroundColor).restore();
5494
+ }
5495
+ renderBorder.call(this, cell.border, cell.borderColor, cell.x, cell.y, cell.width, cell.height);
5496
+ if (cell.debug) {
5497
+ this.document.save();
5498
+ this.document.dash(1, {
5499
+ space: 1
5500
+ }).lineWidth(1).strokeOpacity(0.3);
5501
+ this.document.rect(cell.x, cell.y, cell.width, cell.height).stroke('green');
5502
+ this.document.restore();
5503
+ }
5504
+ if (cell.text) renderCellText.call(this, cell);
5505
+ };
5506
+ if (rowStruct) accessibleCell.call(this, cell, rowStruct, cellRenderer);else cellRenderer();
5507
+ }
5508
+ function renderCellText(cell) {
5509
+ const doc = this.document;
5510
+ const rollbackFont = doc._fontSource;
5511
+ const rollbackFontSize = doc._fontSize;
5512
+ const rollbackFontFamily = doc._fontFamily;
5513
+ if (cell.customFont) {
5514
+ if (cell.font.src) doc.font(cell.font.src, cell.font.family);
5515
+ if (cell.font.size) doc.fontSize(cell.font.size);
5516
+ }
5517
+ const x = cell.textX;
5518
+ const y = cell.textY;
5519
+ const Ah = cell.textAllocatedHeight;
5520
+ const Aw = cell.textAllocatedWidth;
5521
+ const Cw = cell.textBounds.width;
5522
+ const Ch = cell.textBounds.height;
5523
+ const Ox = -cell.textBounds.x;
5524
+ const Oy = -cell.textBounds.y;
5525
+ const PxScale = cell.align.x === 'right' ? 1 : cell.align.x === 'center' ? 0.5 : 0;
5526
+ const Px = (Aw - Cw) * PxScale;
5527
+ const PyScale = cell.align.y === 'bottom' ? 1 : cell.align.y === 'center' ? 0.5 : 0;
5528
+ const Py = (Ah - Ch) * PyScale;
5529
+ const dx = Px + Ox;
5530
+ const dy = Py + Oy;
5531
+ if (cell.debug) {
5532
+ doc.save();
5533
+ doc.dash(1, {
5534
+ space: 1
5535
+ }).lineWidth(1).strokeOpacity(0.3);
5536
+ if (cell.text) {
5537
+ doc.moveTo(x + Px, y).lineTo(x + Px, y + Ah).moveTo(x + Px + Cw, y).lineTo(x + Px + Cw, y + Ah).stroke('blue').moveTo(x, y + Py).lineTo(x + Aw, y + Py).moveTo(x, y + Py + Ch).lineTo(x + Aw, y + Py + Ch).stroke('green');
5538
+ }
5539
+ doc.rect(x, y, Aw, Ah).stroke('orange');
5540
+ doc.restore();
5541
+ }
5542
+ doc.save().rect(x, y, Aw, Ah).clip();
5543
+ doc.fillColor(cell.textColor).strokeColor(cell.textStrokeColor);
5544
+ if (cell.textStroke > 0) doc.lineWidth(cell.textStroke);
5545
+ doc.text(cell.text, x + dx, y + dy, cell.textOptions);
5546
+ doc.restore();
5547
+ if (cell.font) doc.font(rollbackFont, rollbackFontFamily, rollbackFontSize);
5548
+ }
5549
+ function renderBorder(border, borderColor, x, y, width, height, mask) {
5550
+ border = Object.fromEntries(Object.entries(border).map(([k, v]) => [k, mask && !mask[k] ? 0 : v]));
5551
+ const doc = this.document;
5552
+ if ([border.right, border.bottom, border.left].every(val => val === border.top)) {
5553
+ if (border.top > 0) {
5554
+ doc.save().lineWidth(border.top).rect(x, y, width, height).stroke(borderColor.top).restore();
5555
+ }
5556
+ } else {
5557
+ if (border.top > 0) {
5558
+ doc.save().lineWidth(border.top).moveTo(x, y).lineTo(x + width, y).stroke(borderColor.top).restore();
5559
+ }
5560
+ if (border.right > 0) {
5561
+ doc.save().lineWidth(border.right).moveTo(x + width, y).lineTo(x + width, y + height).stroke(borderColor.right).restore();
5562
+ }
5563
+ if (border.bottom > 0) {
5564
+ doc.save().lineWidth(border.bottom).moveTo(x + width, y + height).lineTo(x, y + height).stroke(borderColor.bottom).restore();
5565
+ }
5566
+ if (border.left > 0) {
5567
+ doc.save().lineWidth(border.left).moveTo(x, y + height).lineTo(x, y).stroke(borderColor.left).restore();
5568
+ }
5569
+ }
5570
+ }
5571
+
5572
+ class PDFTable {
5573
+ constructor(document, opts = {}) {
5574
+ this.document = document;
5575
+ this.opts = Object.freeze(opts);
5576
+ normalizeTable.call(this);
5577
+ accommodateTable.call(this);
5578
+ this._currRowIndex = 0;
5579
+ this._ended = false;
5580
+ if (opts.data) {
5581
+ for (const row of opts.data) this.row(row);
5582
+ return this.end();
5583
+ }
5584
+ }
5585
+ row(row, lastRow = false) {
5586
+ if (this._ended) {
5587
+ throw new Error(`Table was marked as ended on row ${this._currRowIndex}`);
5588
+ }
5589
+ row = Array.from(row);
5590
+ row = normalizeRow.call(this, row, this._currRowIndex);
5591
+ if (this._currRowIndex === 0) ensure.call(this, row);
5592
+ const {
5593
+ newPage,
5594
+ toRender
5595
+ } = measure.call(this, row, this._currRowIndex);
5596
+ if (newPage) this.document.continueOnNewPage();
5597
+ const yPos = renderRow.call(this, toRender, this._currRowIndex);
5598
+ this.document.x = this._position.x;
5599
+ this.document.y = yPos;
5600
+ if (lastRow) return this.end();
5601
+ this._currRowIndex++;
5602
+ return this;
5603
+ }
5604
+ end() {
5605
+ while (this._rowBuffer?.size) this.row([]);
5606
+ this._ended = true;
5607
+ accommodateCleanup.call(this);
5608
+ return this.document;
5609
+ }
5610
+ }
5611
+
5612
+ var TableMixin = {
5613
+ initTables() {
5614
+ this._tableIndex = 0;
5615
+ },
5616
+ table(opts) {
5617
+ return new PDFTable(this, opts);
5618
+ }
5619
+ };
5620
+
5383
5621
  class PDFMetadata {
5384
5622
  constructor() {
5385
5623
  this._metadata = `
@@ -5421,7 +5659,7 @@ var MetadataMixin = {
5421
5659
  _addInfo() {
5422
5660
  this.appendXML(`
5423
5661
  <rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/">
5424
- <xmp:CreateDate>${this.info.CreationDate.toISOString().split('.')[0] + "Z"}</xmp:CreateDate>
5662
+ <xmp:CreateDate>${this.info.CreationDate.toISOString().split('.')[0] + 'Z'}</xmp:CreateDate>
5425
5663
  <xmp:CreatorTool>${this.info.Creator}</xmp:CreatorTool>
5426
5664
  </rdf:Description>
5427
5665
  `);
@@ -5474,11 +5712,6 @@ var MetadataMixin = {
5474
5712
  endMetadata() {
5475
5713
  this._addInfo();
5476
5714
  this.metadata.end();
5477
-
5478
- /*
5479
- Metadata was introduced in PDF 1.4, so adding it to 1.3
5480
- will likely only take up more space.
5481
- */
5482
5715
  if (this.version != 1.3) {
5483
5716
  this.metadataRef = this.ref({
5484
5717
  length: this.metadata.getLength(),
@@ -5493,16 +5726,10 @@ var MetadataMixin = {
5493
5726
  }
5494
5727
  };
5495
5728
 
5496
- /*
5497
- PDFDocument - represents an entire PDF document
5498
- By Devon Govett
5499
- */
5500
5729
  class PDFDocument extends stream.Readable {
5501
5730
  constructor(options = {}) {
5502
5731
  super(options);
5503
5732
  this.options = options;
5504
-
5505
- // PDF version
5506
5733
  switch (options.pdfVersion) {
5507
5734
  case '1.4':
5508
5735
  this.version = 1.4;
@@ -5521,13 +5748,9 @@ class PDFDocument extends stream.Readable {
5521
5748
  this.version = 1.3;
5522
5749
  break;
5523
5750
  }
5524
-
5525
- // Whether streams should be compressed
5526
5751
  this.compress = this.options.compress != null ? this.options.compress : true;
5527
5752
  this._pageBuffer = [];
5528
5753
  this._pageBufferStart = 0;
5529
-
5530
- // The PDF object store
5531
5754
  this._offsets = [];
5532
5755
  this._waiting = 0;
5533
5756
  this._ended = false;
@@ -5548,11 +5771,7 @@ class PDFDocument extends stream.Readable {
5548
5771
  if (this.options.lang) {
5549
5772
  this._root.data.Lang = new String(this.options.lang);
5550
5773
  }
5551
-
5552
- // The current page
5553
5774
  this.page = null;
5554
-
5555
- // Initialize mixins
5556
5775
  this.initMetadata();
5557
5776
  this.initColor();
5558
5777
  this.initVector();
@@ -5561,9 +5780,8 @@ class PDFDocument extends stream.Readable {
5561
5780
  this.initImages();
5562
5781
  this.initOutline();
5563
5782
  this.initMarkings(options);
5783
+ this.initTables();
5564
5784
  this.initSubset(options);
5565
-
5566
- // Initialize the metadata
5567
5785
  this.info = {
5568
5786
  Producer: 'PDFKit',
5569
5787
  Creator: 'PDFKit',
@@ -5580,21 +5798,10 @@ class PDFDocument extends stream.Readable {
5580
5798
  DisplayDocTitle: true
5581
5799
  });
5582
5800
  }
5583
-
5584
- // Generate file ID
5585
5801
  this._id = PDFSecurity.generateFileID(this.info);
5586
-
5587
- // Initialize security settings
5588
5802
  this._security = PDFSecurity.create(this, options);
5589
-
5590
- // Write the header
5591
- // PDF version
5592
5803
  this._write(`%PDF-${this.version}`);
5593
-
5594
- // 4 binary chars, as recommended by the spec
5595
5804
  this._write('%\xFF\xFF\xFF\xFF');
5596
-
5597
- // Add the first page
5598
5805
  if (this.options.autoFirstPage !== false) {
5599
5806
  this.addPage();
5600
5807
  }
@@ -5605,27 +5812,16 @@ class PDFDocument extends stream.Readable {
5605
5812
  options
5606
5813
  } = this);
5607
5814
  }
5608
-
5609
- // end the current page if needed
5610
5815
  if (!this.options.bufferPages) {
5611
5816
  this.flushPages();
5612
5817
  }
5613
-
5614
- // create a page object
5615
5818
  this.page = new PDFPage(this, options);
5616
5819
  this._pageBuffer.push(this.page);
5617
-
5618
- // add the page to the object store
5619
5820
  const pages = this._root.data.Pages.data;
5620
5821
  pages.Kids.push(this.page.dictionary);
5621
5822
  pages.Count++;
5622
-
5623
- // reset x and y coordinates
5624
5823
  this.x = this.page.margins.left;
5625
5824
  this.y = this.page.margins.top;
5626
-
5627
- // flip PDF coordinate system so that the origin is in
5628
- // the top left rather than the bottom left
5629
5825
  this._ctm = [1, 0, 0, 1, 0, 0];
5630
5826
  this.transform(1, 0, 0, -1, 0, this.page.height);
5631
5827
  this.emit('pageAdded');
@@ -5633,7 +5829,7 @@ class PDFDocument extends stream.Readable {
5633
5829
  }
5634
5830
  continueOnNewPage(options) {
5635
5831
  const pageMarkings = this.endPageMarkings(this.page);
5636
- this.addPage(options);
5832
+ this.addPage(options ?? this.page._options);
5637
5833
  this.initPageMarkings(pageMarkings);
5638
5834
  return this;
5639
5835
  }
@@ -5651,8 +5847,6 @@ class PDFDocument extends stream.Readable {
5651
5847
  return this.page = page;
5652
5848
  }
5653
5849
  flushPages() {
5654
- // this local variable exists so we're future-proof against
5655
- // reentrant calls to flushPages.
5656
5850
  const pages = this._pageBuffer;
5657
5851
  this._pageBuffer = [];
5658
5852
  this._pageBufferStart += pages.length;
@@ -5673,13 +5867,10 @@ class PDFDocument extends stream.Readable {
5673
5867
  }
5674
5868
  addNamedEmbeddedFile(name, ref) {
5675
5869
  if (!this._root.data.Names.data.EmbeddedFiles) {
5676
- // disabling /Limits for this tree fixes attachments not showing in Adobe Reader
5677
5870
  this._root.data.Names.data.EmbeddedFiles = new PDFNameTree({
5678
5871
  limits: false
5679
5872
  });
5680
5873
  }
5681
-
5682
- // add filespec to EmbeddedFiles
5683
5874
  this._root.data.Names.data.EmbeddedFiles.add(name, ref);
5684
5875
  }
5685
5876
  addNamedJavaScript(name, js) {
@@ -5694,19 +5885,17 @@ class PDFDocument extends stream.Readable {
5694
5885
  }
5695
5886
  ref(data) {
5696
5887
  const ref = new PDFReference(this, this._offsets.length + 1, data);
5697
- this._offsets.push(null); // placeholder for this object's offset once it is finalized
5888
+ this._offsets.push(null);
5698
5889
  this._waiting++;
5699
5890
  return ref;
5700
5891
  }
5701
5892
  _read() {}
5702
- // do nothing, but this method is required by node
5703
-
5704
5893
  _write(data) {
5705
5894
  if (!Buffer.isBuffer(data)) {
5706
5895
  data = Buffer.from(data + '\n', 'binary');
5707
5896
  }
5708
5897
  this.push(data);
5709
- return this._offset += data.length;
5898
+ this._offset += data.length;
5710
5899
  }
5711
5900
  addContent(data) {
5712
5901
  this.page.write(data);
@@ -5716,7 +5905,7 @@ class PDFDocument extends stream.Readable {
5716
5905
  this._offsets[ref.id - 1] = ref.offset;
5717
5906
  if (--this._waiting === 0 && this._ended) {
5718
5907
  this._finalize();
5719
- return this._ended = false;
5908
+ this._ended = false;
5720
5909
  }
5721
5910
  }
5722
5911
  end() {
@@ -5753,13 +5942,12 @@ class PDFDocument extends stream.Readable {
5753
5942
  this._security.end();
5754
5943
  }
5755
5944
  if (this._waiting === 0) {
5756
- return this._finalize();
5945
+ this._finalize();
5757
5946
  } else {
5758
- return this._ended = true;
5947
+ this._ended = true;
5759
5948
  }
5760
5949
  }
5761
5950
  _finalize() {
5762
- // generate xref
5763
5951
  const xRefOffset = this._offset;
5764
5952
  this._write('xref');
5765
5953
  this._write(`0 ${this._offsets.length + 1}`);
@@ -5768,8 +5956,6 @@ class PDFDocument extends stream.Readable {
5768
5956
  offset = `0000000000${offset}`.slice(-10);
5769
5957
  this._write(offset + ' 00000 n ');
5770
5958
  }
5771
-
5772
- // trailer
5773
5959
  const trailer = {
5774
5960
  Size: this._offsets.length + 1,
5775
5961
  Root: this._root,
@@ -5784,9 +5970,7 @@ class PDFDocument extends stream.Readable {
5784
5970
  this._write('startxref');
5785
5971
  this._write(`${xRefOffset}`);
5786
5972
  this._write('%%EOF');
5787
-
5788
- // end the stream
5789
- return this.push(null);
5973
+ this.push(null);
5790
5974
  }
5791
5975
  toString() {
5792
5976
  return '[object PDFDocument]';
@@ -5807,6 +5991,7 @@ mixin(MarkingsMixin);
5807
5991
  mixin(AcroFormMixin);
5808
5992
  mixin(AttachmentsMixin);
5809
5993
  mixin(SubsetMixin);
5994
+ mixin(TableMixin);
5810
5995
  PDFDocument.LineWrapper = LineWrapper;
5811
5996
 
5812
5997
  module.exports = PDFDocument;