pdfkit 0.16.0 → 0.17.1

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,79 @@ 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
+ const fArray = new Float32Array(1);
225
+ const uArray = new Uint32Array(fArray.buffer);
226
+ function PDFNumber(n) {
227
+ const rounded = Math.fround(n);
228
+ if (rounded <= n) return rounded;
229
+ fArray[0] = n;
230
+ if (n <= 0) {
231
+ uArray[0] += 1;
232
+ } else {
233
+ uArray[0] -= 1;
234
+ }
235
+ return fArray[0];
236
+ }
237
+ function normalizeSides(sides, defaultDefinition = undefined, transformer = v => v) {
238
+ if (sides == null || typeof sides === 'object' && Object.keys(sides).length === 0) {
239
+ sides = defaultDefinition;
240
+ }
241
+ if (sides == null || typeof sides !== 'object') {
242
+ sides = {
243
+ top: sides,
244
+ right: sides,
245
+ bottom: sides,
246
+ left: sides
247
+ };
248
+ } else if (Array.isArray(sides)) {
249
+ if (sides.length === 2) {
250
+ sides = {
251
+ vertical: sides[0],
252
+ horizontal: sides[1]
253
+ };
254
+ } else {
255
+ sides = {
256
+ top: sides[0],
257
+ right: sides[1],
258
+ bottom: sides[2],
259
+ left: sides[3]
260
+ };
261
+ }
262
+ }
263
+ if ('vertical' in sides || 'horizontal' in sides) {
264
+ sides = {
265
+ top: sides.vertical,
266
+ right: sides.horizontal,
267
+ bottom: sides.vertical,
268
+ left: sides.horizontal
269
+ };
270
+ }
271
+ return {
272
+ top: transformer(sides.top),
273
+ right: transformer(sides.right),
274
+ bottom: transformer(sides.bottom),
275
+ left: transformer(sides.left)
276
+ };
277
+ }
278
+ const MM_TO_CM = 1 / 10;
279
+ const CM_TO_IN = 1 / 2.54;
280
+ const PX_TO_IN = 1 / 96;
281
+ const IN_TO_PT = 72;
282
+ const PC_TO_PT = 12;
283
+ function cosine(a) {
284
+ if (a === 0) return 1;
285
+ if (a === 90) return 0;
286
+ if (a === 180) return -1;
287
+ if (a === 270) return 0;
288
+ return Math.cos(a * Math.PI / 180);
289
+ }
290
+ function sine(a) {
291
+ if (a === 0) return 0;
292
+ if (a === 90) return 1;
293
+ if (a === 180) return 0;
294
+ if (a === 270) return -1;
295
+ return Math.sin(a * Math.PI / 180);
296
+ }
265
297
 
266
298
  const DEFAULT_MARGINS = {
267
299
  top: 72,
@@ -324,35 +356,19 @@ const SIZES = {
324
356
  class PDFPage {
325
357
  constructor(document, options = {}) {
326
358
  this.document = document;
359
+ this._options = options;
327
360
  this.size = options.size || 'letter';
328
361
  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
362
  const dimensions = Array.isArray(this.size) ? this.size : SIZES[this.size.toUpperCase()];
346
363
  this.width = dimensions[this.layout === 'portrait' ? 0 : 1];
347
364
  this.height = dimensions[this.layout === 'portrait' ? 1 : 0];
348
365
  this.content = this.document.ref();
349
-
350
- // Initialize the Font, XObject, and ExtGState dictionaries
366
+ if (options.font) document.font(options.font, options.fontFamily);
367
+ if (options.fontSize) document.fontSize(options.fontSize);
368
+ this.margins = normalizeSides(options.margin ?? options.margins, DEFAULT_MARGINS, x => document.sizeToPoint(x, 0, this));
351
369
  this.resources = this.document.ref({
352
370
  ProcSet: ['PDF', 'Text', 'ImageB', 'ImageC', 'ImageI']
353
371
  });
354
-
355
- // The page dictionary
356
372
  this.dictionary = this.document.ref({
357
373
  Type: 'Page',
358
374
  Parent: this.document._root.data.Pages,
@@ -362,8 +378,6 @@ class PDFPage {
362
378
  });
363
379
  this.markings = [];
364
380
  }
365
-
366
- // Lazily create these objects
367
381
  get fonts() {
368
382
  const data = this.resources.data;
369
383
  return data.Font != null ? data.Font : data.Font = {};
@@ -392,14 +406,18 @@ class PDFPage {
392
406
  const data = this.dictionary.data;
393
407
  return data.StructParents != null ? data.StructParents : data.StructParents = this.document.createStructParentTreeNextKey();
394
408
  }
409
+ get contentWidth() {
410
+ return this.width - this.margins.left - this.margins.right;
411
+ }
412
+ get contentHeight() {
413
+ return this.height - this.margins.top - this.margins.bottom;
414
+ }
395
415
  maxY() {
396
416
  return this.height - this.margins.bottom;
397
417
  }
398
418
  write(chunk) {
399
419
  return this.content.write(chunk);
400
420
  }
401
-
402
- // Set tab order if document is tagged for accessibility.
403
421
  _setTabOrder() {
404
422
  if (!this.dictionary.Tabs && this.document.hasMarkInfoDictionary()) {
405
423
  this.dictionary.data.Tabs = 'S';
@@ -417,191 +435,57 @@ class PDFPage {
417
435
  }
418
436
  }
419
437
 
420
- /*
421
- PDFNameTree - represents a name tree object
422
- */
423
438
  class PDFNameTree extends PDFTree {
424
439
  _compareKeys(a, b) {
425
440
  return a.localeCompare(b);
426
441
  }
427
442
  _keysName() {
428
- return "Names";
443
+ return 'Names';
429
444
  }
430
445
  _dataForKey(k) {
431
446
  return new String(k);
432
447
  }
433
448
  }
434
449
 
435
- /**
436
- * Check if value is in a range group.
437
- * @param {number} value
438
- * @param {number[]} rangeGroup
439
- * @returns {boolean}
440
- */
441
450
  function inRange(value, rangeGroup) {
442
451
  if (value < rangeGroup[0]) return false;
443
452
  let startRange = 0;
444
453
  let endRange = rangeGroup.length / 2;
445
454
  while (startRange <= endRange) {
446
455
  const middleRange = Math.floor((startRange + endRange) / 2);
447
-
448
- // actual array index
449
456
  const arrayIndex = middleRange * 2;
450
-
451
- // Check if value is in range pointed by actual index
452
457
  if (value >= rangeGroup[arrayIndex] && value <= rangeGroup[arrayIndex + 1]) {
453
458
  return true;
454
459
  }
455
460
  if (value > rangeGroup[arrayIndex + 1]) {
456
- // Search Right Side Of Array
457
461
  startRange = middleRange + 1;
458
462
  } else {
459
- // Search Left Side Of Array
460
463
  endRange = middleRange - 1;
461
464
  }
462
465
  }
463
466
  return false;
464
467
  }
465
468
 
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
469
  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
470
  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
471
  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
472
  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
-
473
+ 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
474
  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
-
475
+ 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];
476
+ 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];
477
+ 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
478
  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
479
  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
480
  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
481
  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
482
  const isBidirectionalL = character => inRange(character, bidirectional_l);
578
483
 
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
484
  const mapping2space = isNonASCIISpaceCharacter;
586
-
587
- /**
588
- * the "commonly mapped to nothing" characters [StringPrep, B.1]
589
- * that can be mapped to nothing.
590
- */
591
485
  const mapping2nothing = isCommonlyMappedToNothing;
592
-
593
- // utils
594
486
  const getCodePoint = character => character.codePointAt(0);
595
487
  const first = x => x[0];
596
488
  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
489
  function toCodePoints(input) {
606
490
  const codepoints = [];
607
491
  const size = input.length;
@@ -619,14 +503,6 @@ function toCodePoints(input) {
619
503
  }
620
504
  return codepoints;
621
505
  }
622
-
623
- /**
624
- * SASLprep.
625
- * @param {string} input
626
- * @param {Object} opts
627
- * @param {boolean} opts.allowUnassigned
628
- * @returns {string}
629
- */
630
506
  function saslprep(input, opts = {}) {
631
507
  if (typeof input !== 'string') {
632
508
  throw new TypeError('Expected string.');
@@ -634,49 +510,24 @@ function saslprep(input, opts = {}) {
634
510
  if (input.length === 0) {
635
511
  return '';
636
512
  }
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
513
+ const mapped_input = toCodePoints(input).map(character => mapping2space(character) ? 0x20 : character).filter(character => !mapping2nothing(character));
646
514
  const normalized_input = String.fromCodePoint.apply(null, mapped_input).normalize('NFKC');
647
515
  const normalized_map = toCodePoints(normalized_input);
648
-
649
- // 3. Prohibit
650
516
  const hasProhibited = normalized_map.some(isProhibitedCharacter);
651
517
  if (hasProhibited) {
652
518
  throw new Error('Prohibited character, see https://tools.ietf.org/html/rfc4013#section-2.3');
653
519
  }
654
-
655
- // Unassigned Code Points
656
520
  if (opts.allowUnassigned !== true) {
657
521
  const hasUnassigned = normalized_map.some(isUnassignedCodePoint);
658
522
  if (hasUnassigned) {
659
523
  throw new Error('Unassigned code point, see https://tools.ietf.org/html/rfc4013#section-2.5');
660
524
  }
661
525
  }
662
-
663
- // 4. check bidi
664
-
665
526
  const hasBidiRAL = normalized_map.some(isBidirectionalRAL);
666
527
  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
528
  if (hasBidiRAL && hasBidiL) {
671
529
  throw new Error('String must not contain RandALCat and LCat at the same time,' + ' see https://tools.ietf.org/html/rfc3454#section-6');
672
530
  }
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
531
  const isFirstBidiRAL = isBidirectionalRAL(getCodePoint(first(normalized_input)));
681
532
  const isLastBidiRAL = isBidirectionalRAL(getCodePoint(last(normalized_input)));
682
533
  if (hasBidiRAL && !(isFirstBidiRAL && isLastBidiRAL)) {
@@ -685,15 +536,10 @@ function saslprep(input, opts = {}) {
685
536
  return normalized_input;
686
537
  }
687
538
 
688
- /*
689
- PDFSecurity - represents PDF security settings
690
- By Yang Liu <hi@zesik.com>
691
- */
692
539
  class PDFSecurity {
693
540
  static generateFileID(info = {}) {
694
541
  let infoStr = `${info.CreationDate.getTime()}\n`;
695
542
  for (let key in info) {
696
- // eslint-disable-next-line no-prototype-builtins
697
543
  if (!info.hasOwnProperty(key)) {
698
544
  continue;
699
545
  }
@@ -1067,8 +913,6 @@ class PDFGradient$1 {
1067
913
  }
1068
914
  this.embedded = true;
1069
915
  this.matrix = m;
1070
-
1071
- // if the last stop comes before 100%, add a copy at 100%
1072
916
  const last = this.stops[stopsLength - 1];
1073
917
  if (last[0] < 1) {
1074
918
  this.stops.push([1, last[1], last[2]]);
@@ -1091,14 +935,11 @@ class PDFGradient$1 {
1091
935
  stops.push(fn);
1092
936
  fn.end();
1093
937
  }
1094
-
1095
- // if there are only two stops, we don't need a stitching function
1096
938
  if (stopsLength === 1) {
1097
939
  fn = stops[0];
1098
940
  } else {
1099
941
  fn = this.doc.ref({
1100
942
  FunctionType: 3,
1101
- // stitching function
1102
943
  Domain: [0, 1],
1103
944
  Functions: stops,
1104
945
  Bounds: bounds,
@@ -1179,7 +1020,6 @@ class PDFGradient$1 {
1179
1020
  return pattern;
1180
1021
  }
1181
1022
  apply(stroke) {
1182
- // apply gradient transform to existing document ctm
1183
1023
  const [m0, m1, m2, m3, m4, m5] = this.doc._ctm;
1184
1024
  const [m11, m12, m21, m22, dx, dy] = this.transform;
1185
1025
  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 +1082,6 @@ var Gradient = {
1242
1082
  PDFRadialGradient: PDFRadialGradient$1
1243
1083
  };
1244
1084
 
1245
- /*
1246
- PDF tiling pattern support. Uncolored only.
1247
- */
1248
-
1249
1085
  const underlyingColorSpaces = ['DeviceCMYK', 'DeviceRGB'];
1250
1086
  class PDFTilingPattern$1 {
1251
1087
  constructor(doc, bBox, xStep, yStep, stream) {
@@ -1256,23 +1092,16 @@ class PDFTilingPattern$1 {
1256
1092
  this.stream = stream;
1257
1093
  }
1258
1094
  createPattern() {
1259
- // no resources needed for our current usage
1260
- // required entry
1261
1095
  const resources = this.doc.ref();
1262
1096
  resources.end();
1263
- // apply default transform matrix (flipped in the default doc._ctm)
1264
- // see document.js & gradient.js
1265
1097
  const [m0, m1, m2, m3, m4, m5] = this.doc._ctm;
1266
1098
  const [m11, m12, m21, m22, dx, dy] = [1, 0, 0, 1, 0, 0];
1267
1099
  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
1100
  const pattern = this.doc.ref({
1269
1101
  Type: 'Pattern',
1270
1102
  PatternType: 1,
1271
- // tiling
1272
1103
  PaintType: 2,
1273
- // 1-colored, 2-uncolored
1274
1104
  TilingType: 2,
1275
- // 2-no distortion
1276
1105
  BBox: this.bBox,
1277
1106
  XStep: this.xStep,
1278
1107
  YStep: this.yStep,
@@ -1283,8 +1112,6 @@ class PDFTilingPattern$1 {
1283
1112
  return pattern;
1284
1113
  }
1285
1114
  embedPatternColorSpaces() {
1286
- // map each pattern to an underlying color space
1287
- // and embed on each page
1288
1115
  underlyingColorSpaces.forEach(csName => {
1289
1116
  const csId = this.getPatternColorSpaceId(csName);
1290
1117
  if (this.doc.page.colorSpaces[csId]) return;
@@ -1302,24 +1129,17 @@ class PDFTilingPattern$1 {
1302
1129
  this.id = 'P' + this.doc._patternCount;
1303
1130
  this.pattern = this.createPattern();
1304
1131
  }
1305
-
1306
- // patterns are embedded in each page
1307
1132
  if (!this.doc.page.patterns[this.id]) {
1308
1133
  this.doc.page.patterns[this.id] = this.pattern;
1309
1134
  }
1310
1135
  }
1311
1136
  apply(stroke, patternColor) {
1312
- // do any embedding/creating that might be needed
1313
1137
  this.embedPatternColorSpaces();
1314
1138
  this.embed();
1315
1139
  const normalizedColor = this.doc._normalizeColor(patternColor);
1316
1140
  if (!normalizedColor) throw Error(`invalid pattern color. (value: ${patternColor})`);
1317
-
1318
- // select one of the pattern color spaces
1319
1141
  const csId = this.getPatternColorSpaceId(this.doc._getColorSpace(normalizedColor));
1320
1142
  this.doc._setColorSpace(csId, stroke);
1321
-
1322
- // stroke/fill using the pattern and color (in the above underlying color space)
1323
1143
  const op = stroke ? 'SCN' : 'scn';
1324
1144
  return this.doc.addContent(`${normalizedColor.join(' ')} /${this.id} ${op}`);
1325
1145
  }
@@ -1339,11 +1159,10 @@ const {
1339
1159
  var ColorMixin = {
1340
1160
  initColor() {
1341
1161
  this.spotColors = {};
1342
- // The opacity dictionaries
1343
1162
  this._opacityRegistry = {};
1344
1163
  this._opacityCount = 0;
1345
1164
  this._patternCount = 0;
1346
- return this._gradCount = 0;
1165
+ this._gradCount = 0;
1347
1166
  },
1348
1167
  _normalizeColor(color) {
1349
1168
  if (typeof color === 'string') {
@@ -1360,10 +1179,8 @@ var ColorMixin = {
1360
1179
  }
1361
1180
  }
1362
1181
  if (Array.isArray(color)) {
1363
- // RGB
1364
1182
  if (color.length === 3) {
1365
1183
  color = color.map(part => part / 255);
1366
- // CMYK
1367
1184
  } else if (color.length === 4) {
1368
1185
  color = color.map(part => part / 100);
1369
1186
  }
@@ -1375,12 +1192,10 @@ var ColorMixin = {
1375
1192
  if (color instanceof PDFGradient) {
1376
1193
  color.apply(stroke);
1377
1194
  return true;
1378
- // see if tiling pattern, decode & apply it it
1379
1195
  } else if (Array.isArray(color) && color[0] instanceof PDFTilingPattern) {
1380
1196
  color[0].apply(stroke, color[1]);
1381
1197
  return true;
1382
1198
  }
1383
- // any other case should be a normal color and not a pattern
1384
1199
  return this._setColorCore(color, stroke);
1385
1200
  },
1386
1201
  _setColorCore(color, stroke) {
@@ -1414,9 +1229,6 @@ var ColorMixin = {
1414
1229
  if (set) {
1415
1230
  this.fillOpacity(opacity);
1416
1231
  }
1417
-
1418
- // save this for text wrapper, which needs to reset
1419
- // the fill color on new pages
1420
1232
  this._fillColor = [color, opacity];
1421
1233
  return this;
1422
1234
  },
@@ -1672,7 +1484,6 @@ const parse = function (path) {
1672
1484
  if (parameters[c] != null) {
1673
1485
  params = parameters[c];
1674
1486
  if (cmd) {
1675
- // save existing command
1676
1487
  if (curArg.length > 0) {
1677
1488
  args[args.length] = +curArg;
1678
1489
  }
@@ -1690,14 +1501,11 @@ const parse = function (path) {
1690
1501
  continue;
1691
1502
  }
1692
1503
  if (args.length === params) {
1693
- // handle reused commands
1694
1504
  ret[ret.length] = {
1695
1505
  cmd,
1696
1506
  args
1697
1507
  };
1698
1508
  args = [+curArg];
1699
-
1700
- // handle assumed commands
1701
1509
  if (cmd === 'M') {
1702
1510
  cmd = 'L';
1703
1511
  }
@@ -1708,8 +1516,6 @@ const parse = function (path) {
1708
1516
  args[args.length] = +curArg;
1709
1517
  }
1710
1518
  foundDecimal = c === '.';
1711
-
1712
- // fix for negative numbers or repeated decimals with no delimeter between commands
1713
1519
  curArg = ['-', '.'].includes(c) ? c : '';
1714
1520
  } else {
1715
1521
  curArg += c;
@@ -1718,18 +1524,13 @@ const parse = function (path) {
1718
1524
  }
1719
1525
  }
1720
1526
  }
1721
-
1722
- // add the last command
1723
1527
  if (curArg.length > 0) {
1724
1528
  if (args.length === params) {
1725
- // handle reused commands
1726
1529
  ret[ret.length] = {
1727
1530
  cmd,
1728
1531
  args
1729
1532
  };
1730
1533
  args = [+curArg];
1731
-
1732
- // handle assumed commands
1733
1534
  if (cmd === 'M') {
1734
1535
  cmd = 'L';
1735
1536
  }
@@ -1747,10 +1548,7 @@ const parse = function (path) {
1747
1548
  return ret;
1748
1549
  };
1749
1550
  const apply = function (commands, doc) {
1750
- // current point, control point, and subpath starting point
1751
1551
  cx = cy = px = py = sx = sy = 0;
1752
-
1753
- // run the commands
1754
1552
  for (let i = 0; i < commands.length; i++) {
1755
1553
  const c = commands[i];
1756
1554
  if (typeof runners[c.cmd] === 'function') {
@@ -1914,8 +1712,6 @@ const solveArc = function (doc, x, y, coords) {
1914
1712
  doc.bezierCurveTo(...bez);
1915
1713
  }
1916
1714
  };
1917
-
1918
- // from Inkscape svgtopdf, thanks!
1919
1715
  const arcToSegments = function (x, y, rx, ry, large, sweep, rotateX, ox, oy) {
1920
1716
  const th = rotateX * (Math.PI / 180);
1921
1717
  const sin_th = Math.sin(th);
@@ -1991,18 +1787,14 @@ class SVGPath {
1991
1787
  const {
1992
1788
  number: number$1
1993
1789
  } = PDFObject;
1994
-
1995
- // This constant is used to approximate a symmetrical arc using a cubic
1996
- // Bezier curve.
1997
1790
  const KAPPA = 4.0 * ((Math.sqrt(2) - 1.0) / 3.0);
1998
1791
  var VectorMixin = {
1999
1792
  initVector() {
2000
- this._ctm = [1, 0, 0, 1, 0, 0]; // current transformation matrix
2001
- return this._ctmStack = [];
1793
+ this._ctm = [1, 0, 0, 1, 0, 0];
1794
+ this._ctmStack = [];
2002
1795
  },
2003
1796
  save() {
2004
1797
  this._ctmStack.push(this._ctm.slice());
2005
- // TODO: save/restore colorspace and styles so not setting it unnessesarily all the time?
2006
1798
  return this.addContent('q');
2007
1799
  },
2008
1800
  restore() {
@@ -2075,8 +1867,6 @@ var VectorMixin = {
2075
1867
  r = 0;
2076
1868
  }
2077
1869
  r = Math.min(r, 0.5 * w, 0.5 * h);
2078
-
2079
- // amount to inset control points from corners (see `ellipse`)
2080
1870
  const c = r * (1.0 - KAPPA);
2081
1871
  this.moveTo(x + r, y);
2082
1872
  this.lineTo(x + w - r, y);
@@ -2090,7 +1880,6 @@ var VectorMixin = {
2090
1880
  return this.closePath();
2091
1881
  },
2092
1882
  ellipse(x, y, r1, r2) {
2093
- // based on http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas/2173084#2173084
2094
1883
  if (r2 == null) {
2095
1884
  r2 = r1;
2096
1885
  }
@@ -2120,10 +1909,8 @@ var VectorMixin = {
2120
1909
  const HALF_PI = 0.5 * Math.PI;
2121
1910
  let deltaAng = endAngle - startAngle;
2122
1911
  if (Math.abs(deltaAng) > TWO_PI) {
2123
- // draw only full circle if more than that is specified
2124
1912
  deltaAng = TWO_PI;
2125
1913
  } else if (deltaAng !== 0 && anticlockwise !== deltaAng < 0) {
2126
- // necessary to flip direction of rendering
2127
1914
  const dir = anticlockwise ? -1 : 1;
2128
1915
  deltaAng = dir * TWO_PI + deltaAng;
2129
1916
  }
@@ -2131,38 +1918,21 @@ var VectorMixin = {
2131
1918
  const segAng = deltaAng / numSegs;
2132
1919
  const handleLen = segAng / HALF_PI * KAPPA * radius;
2133
1920
  let curAng = startAngle;
2134
-
2135
- // component distances between anchor point and control point
2136
1921
  let deltaCx = -Math.sin(curAng) * handleLen;
2137
1922
  let deltaCy = Math.cos(curAng) * handleLen;
2138
-
2139
- // anchor point
2140
1923
  let ax = x + Math.cos(curAng) * radius;
2141
1924
  let ay = y + Math.sin(curAng) * radius;
2142
-
2143
- // calculate and render segments
2144
1925
  this.moveTo(ax, ay);
2145
1926
  for (let segIdx = 0; segIdx < numSegs; segIdx++) {
2146
- // starting control point
2147
1927
  const cp1x = ax + deltaCx;
2148
1928
  const cp1y = ay + deltaCy;
2149
-
2150
- // step angle
2151
1929
  curAng += segAng;
2152
-
2153
- // next anchor point
2154
1930
  ax = x + Math.cos(curAng) * radius;
2155
1931
  ay = y + Math.sin(curAng) * radius;
2156
-
2157
- // next control point delta
2158
1932
  deltaCx = -Math.sin(curAng) * handleLen;
2159
1933
  deltaCy = Math.cos(curAng) * handleLen;
2160
-
2161
- // ending control point
2162
1934
  const cp2x = ax - deltaCx;
2163
1935
  const cp2y = ay - deltaCy;
2164
-
2165
- // render segment
2166
1936
  this.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, ax, ay);
2167
1937
  }
2168
1938
  return this;
@@ -2223,9 +1993,7 @@ var VectorMixin = {
2223
1993
  return this.addContent(`W${this._windingRule(rule)} n`);
2224
1994
  },
2225
1995
  transform(m11, m12, m21, m22, dx, dy) {
2226
- // keep track of the current transformation matrix
2227
1996
  if (m11 === 1 && m12 === 0 && m21 === 0 && m22 === 1 && dx === 0 && dy === 0) {
2228
- // Ignore identity transforms
2229
1997
  return this;
2230
1998
  }
2231
1999
  const m = this._ctm;
@@ -2379,21 +2147,12 @@ oslash ugrave uacute ucircumflex
2379
2147
  udieresis yacute thorn ydieresis\
2380
2148
  `.split(/\s+/);
2381
2149
  class AFMFont {
2382
- static open(filename) {
2383
- return new AFMFont(fs.readFileSync(filename, 'utf8'));
2384
- }
2385
2150
  constructor(contents) {
2386
- this.contents = contents;
2387
2151
  this.attributes = {};
2388
2152
  this.glyphWidths = {};
2389
2153
  this.boundingBoxes = {};
2390
2154
  this.kernPairs = {};
2391
- this.parse();
2392
- // todo: remove charWidths since appears to not be used
2393
- this.charWidths = new Array(256);
2394
- for (let char = 0; char <= 255; char++) {
2395
- this.charWidths[char] = this.glyphWidths[characters[char]];
2396
- }
2155
+ this.parse(contents);
2397
2156
  this.bbox = this.attributes['FontBBox'].split(/\s+/).map(e => +e);
2398
2157
  this.ascender = +(this.attributes['Ascender'] || 0);
2399
2158
  this.descender = +(this.attributes['Descender'] || 0);
@@ -2401,9 +2160,9 @@ class AFMFont {
2401
2160
  this.capHeight = +(this.attributes['CapHeight'] || 0);
2402
2161
  this.lineGap = this.bbox[3] - this.bbox[1] - (this.ascender - this.descender);
2403
2162
  }
2404
- parse() {
2163
+ parse(contents) {
2405
2164
  let section = '';
2406
- for (let line of this.contents.split('\n')) {
2165
+ for (let line of contents.split('\n')) {
2407
2166
  var match;
2408
2167
  var a;
2409
2168
  if (match = line.match(/^Start(\w+)/)) {
@@ -2496,21 +2255,17 @@ class PDFFont {
2496
2255
  return;
2497
2256
  }
2498
2257
  this.embed();
2499
- return this.embedded = true;
2258
+ this.embedded = true;
2500
2259
  }
2501
2260
  embed() {
2502
2261
  throw new Error('Must be implemented by subclasses');
2503
2262
  }
2504
- lineHeight(size, includeGap) {
2505
- if (includeGap == null) {
2506
- includeGap = false;
2507
- }
2263
+ lineHeight(size, includeGap = false) {
2508
2264
  const gap = includeGap ? this.lineGap : 0;
2509
2265
  return (this.ascender + gap - this.descender) / 1000 * size;
2510
2266
  }
2511
2267
  }
2512
2268
 
2513
- // This insanity is so bundlers can inline the font files
2514
2269
  const STANDARD_FONTS = {
2515
2270
  Courier() {
2516
2271
  return fs.readFileSync(__dirname + '/data/Courier.afm', 'utf8');
@@ -2638,8 +2393,6 @@ class EmbeddedFont extends PDFFont {
2638
2393
  }
2639
2394
  layoutRun(text, features) {
2640
2395
  const run = this.font.layout(text, features);
2641
-
2642
- // Normalize position values
2643
2396
  for (let i = 0; i < run.positions.length; i++) {
2644
2397
  const position = run.positions[i];
2645
2398
  for (let key in position) {
@@ -2662,16 +2415,12 @@ class EmbeddedFont extends PDFFont {
2662
2415
  return run;
2663
2416
  }
2664
2417
  layout(text, features, onlyWidth) {
2665
- // Skip the cache if any user defined features are applied
2666
2418
  if (features) {
2667
2419
  return this.layoutRun(text, features);
2668
2420
  }
2669
2421
  let glyphs = onlyWidth ? null : [];
2670
2422
  let positions = onlyWidth ? null : [];
2671
2423
  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
2424
  let last = 0;
2676
2425
  let index = 0;
2677
2426
  while (index <= text.length) {
@@ -2733,17 +2482,15 @@ class EmbeddedFont extends PDFFont {
2733
2482
  if (1 <= familyClass && familyClass <= 7) {
2734
2483
  flags |= 1 << 1;
2735
2484
  }
2736
- flags |= 1 << 2; // assume the font uses non-latin characters
2485
+ flags |= 1 << 2;
2737
2486
  if (familyClass === 10) {
2738
2487
  flags |= 1 << 3;
2739
2488
  }
2740
2489
  if (this.font.head.macStyle.italic) {
2741
2490
  flags |= 1 << 6;
2742
2491
  }
2743
-
2744
- // generate a tag (6 uppercase letters. 17 is the char code offset from '0' to 'A'. 73 will map to 'Z')
2745
2492
  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(' ', '_');
2493
+ const name = tag + '+' + this.font.postscriptName?.replaceAll(' ', '_');
2747
2494
  const {
2748
2495
  bbox
2749
2496
  } = this.font;
@@ -2758,8 +2505,7 @@ class EmbeddedFont extends PDFFont {
2758
2505
  CapHeight: (this.font.capHeight || this.font.ascent) * this.scale,
2759
2506
  XHeight: (this.font.xHeight || 0) * this.scale,
2760
2507
  StemV: 0
2761
- }); // not sure how to calculate this
2762
-
2508
+ });
2763
2509
  if (isCFF) {
2764
2510
  descriptor.data.FontFile3 = fontFile;
2765
2511
  } else {
@@ -2801,17 +2547,11 @@ class EmbeddedFont extends PDFFont {
2801
2547
  };
2802
2548
  return this.dictionary.end();
2803
2549
  }
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
2550
  toUnicodeCmap() {
2809
2551
  const cmap = this.document.ref();
2810
2552
  const entries = [];
2811
2553
  for (let codePoints of this.unicode) {
2812
2554
  const encoded = [];
2813
-
2814
- // encode codePoints to utf16
2815
2555
  for (let value of codePoints) {
2816
2556
  if (value > 0xffff) {
2817
2557
  value -= 0x10000;
@@ -2878,31 +2618,26 @@ class PDFFontFactory {
2878
2618
  }
2879
2619
 
2880
2620
  const isEqualFont = (font1, font2) => {
2881
- // compare font checksum
2882
2621
  if (font1.font._tables?.head?.checkSumAdjustment !== font2.font._tables?.head?.checkSumAdjustment) {
2883
2622
  return false;
2884
2623
  }
2885
-
2886
- // compare font name table
2887
2624
  if (JSON.stringify(font1.font._tables?.name?.records) !== JSON.stringify(font2.font._tables?.name?.records)) {
2888
2625
  return false;
2889
2626
  }
2890
2627
  return true;
2891
2628
  };
2892
2629
  var FontsMixin = {
2893
- initFonts(defaultFont = 'Helvetica') {
2894
- // Lookup table for embedded fonts
2630
+ initFonts(defaultFont = 'Helvetica', defaultFontFamily = null, defaultFontSize = 12) {
2895
2631
  this._fontFamilies = {};
2896
2632
  this._fontCount = 0;
2897
-
2898
- // Font state
2899
- this._fontSize = 12;
2633
+ this._fontSource = defaultFont;
2634
+ this._fontFamily = defaultFontFamily;
2635
+ this._fontSize = defaultFontSize;
2900
2636
  this._font = null;
2637
+ this._remSize = defaultFontSize;
2901
2638
  this._registeredFonts = {};
2902
-
2903
- // Set the default font
2904
2639
  if (defaultFont) {
2905
- this.font(defaultFont);
2640
+ this.font(defaultFont, defaultFontFamily);
2906
2641
  }
2907
2642
  },
2908
2643
  font(src, family, size) {
@@ -2911,8 +2646,6 @@ var FontsMixin = {
2911
2646
  size = family;
2912
2647
  family = null;
2913
2648
  }
2914
-
2915
- // check registered fonts if src is a string
2916
2649
  if (typeof src === 'string' && this._registeredFonts[src]) {
2917
2650
  cacheKey = src;
2918
2651
  ({
@@ -2925,28 +2658,21 @@ var FontsMixin = {
2925
2658
  cacheKey = null;
2926
2659
  }
2927
2660
  }
2661
+ this._fontSource = src;
2662
+ this._fontFamily = family;
2928
2663
  if (size != null) {
2929
2664
  this.fontSize(size);
2930
2665
  }
2931
-
2932
- // fast path: check if the font is already in the PDF
2933
2666
  if (font = this._fontFamilies[cacheKey]) {
2934
2667
  this._font = font;
2935
2668
  return this;
2936
2669
  }
2937
-
2938
- // load the font
2939
2670
  const id = `F${++this._fontCount}`;
2940
2671
  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
2672
  if ((font = this._fontFamilies[this._font.name]) && isEqualFont(this._font, font)) {
2945
2673
  this._font = font;
2946
2674
  return this;
2947
2675
  }
2948
-
2949
- // save the font for reuse later
2950
2676
  if (cacheKey) {
2951
2677
  this._fontFamilies[cacheKey] = this._font;
2952
2678
  }
@@ -2956,13 +2682,10 @@ var FontsMixin = {
2956
2682
  return this;
2957
2683
  },
2958
2684
  fontSize(_fontSize) {
2959
- this._fontSize = _fontSize;
2685
+ this._fontSize = this.sizeToPoint(_fontSize);
2960
2686
  return this;
2961
2687
  },
2962
2688
  currentLineHeight(includeGap) {
2963
- if (includeGap == null) {
2964
- includeGap = false;
2965
- }
2966
2689
  return this._font.lineHeight(this._fontSize, includeGap);
2967
2690
  },
2968
2691
  registerFont(name, src, family) {
@@ -2971,6 +2694,64 @@ var FontsMixin = {
2971
2694
  family
2972
2695
  };
2973
2696
  return this;
2697
+ },
2698
+ sizeToPoint(size, defaultValue = 0, page = this.page, percentageWidth = undefined) {
2699
+ if (!percentageWidth) percentageWidth = this._fontSize;
2700
+ if (typeof defaultValue !== 'number') defaultValue = this.sizeToPoint(defaultValue);
2701
+ if (size === undefined) return defaultValue;
2702
+ if (typeof size === 'number') return size;
2703
+ if (typeof size === 'boolean') return Number(size);
2704
+ const match = String(size).match(/((\d+)?(\.\d+)?)(em|in|px|cm|mm|pc|ex|ch|rem|vw|vh|vmin|vmax|%|pt)?/);
2705
+ if (!match) throw new Error(`Unsupported size '${size}'`);
2706
+ let multiplier;
2707
+ switch (match[4]) {
2708
+ case 'em':
2709
+ multiplier = this._fontSize;
2710
+ break;
2711
+ case 'in':
2712
+ multiplier = IN_TO_PT;
2713
+ break;
2714
+ case 'px':
2715
+ multiplier = PX_TO_IN * IN_TO_PT;
2716
+ break;
2717
+ case 'cm':
2718
+ multiplier = CM_TO_IN * IN_TO_PT;
2719
+ break;
2720
+ case 'mm':
2721
+ multiplier = MM_TO_CM * CM_TO_IN * IN_TO_PT;
2722
+ break;
2723
+ case 'pc':
2724
+ multiplier = PC_TO_PT;
2725
+ break;
2726
+ case 'ex':
2727
+ multiplier = this.currentLineHeight();
2728
+ break;
2729
+ case 'ch':
2730
+ multiplier = this.widthOfString('0');
2731
+ break;
2732
+ case 'rem':
2733
+ multiplier = this._remSize;
2734
+ break;
2735
+ case 'vw':
2736
+ multiplier = page.width / 100;
2737
+ break;
2738
+ case 'vh':
2739
+ multiplier = page.height / 100;
2740
+ break;
2741
+ case 'vmin':
2742
+ multiplier = Math.min(page.width, page.height) / 100;
2743
+ break;
2744
+ case 'vmax':
2745
+ multiplier = Math.max(page.width, page.height) / 100;
2746
+ break;
2747
+ case '%':
2748
+ multiplier = percentageWidth / 100;
2749
+ break;
2750
+ case 'pt':
2751
+ default:
2752
+ multiplier = 1;
2753
+ }
2754
+ return multiplier * Number(match[1]);
2974
2755
  }
2975
2756
  };
2976
2757
 
@@ -2985,7 +2766,7 @@ class LineWrapper extends events.EventEmitter {
2985
2766
  this.characterSpacing = (options.characterSpacing || 0) * this.horizontalScaling / 100;
2986
2767
  this.wordSpacing = (options.wordSpacing === 0) * this.horizontalScaling / 100;
2987
2768
  this.columns = options.columns || 1;
2988
- this.columnGap = (options.columnGap != null ? options.columnGap : 18) * this.horizontalScaling / 100; // 1/4 inch
2769
+ this.columnGap = (options.columnGap != null ? options.columnGap : 18) * this.horizontalScaling / 100;
2989
2770
  this.lineWidth = (options.width * this.horizontalScaling / 100 - this.columnGap * (this.columns - 1)) / this.columns;
2990
2771
  this.spaceLeft = this.lineWidth;
2991
2772
  this.startX = this.document.x;
@@ -2994,44 +2775,30 @@ class LineWrapper extends events.EventEmitter {
2994
2775
  this.ellipsis = options.ellipsis;
2995
2776
  this.continuedX = 0;
2996
2777
  this.features = options.features;
2997
-
2998
- // calculate the maximum Y position the text can appear at
2999
2778
  if (options.height != null) {
3000
2779
  this.height = options.height;
3001
- this.maxY = this.startY + options.height;
2780
+ this.maxY = PDFNumber(this.startY + options.height);
3002
2781
  } else {
3003
- this.maxY = this.document.page.maxY();
2782
+ this.maxY = PDFNumber(this.document.page.maxY());
3004
2783
  }
3005
-
3006
- // handle paragraph indents
3007
2784
  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
2785
  const indent = this.continuedX || this.indent;
3012
2786
  this.document.x += indent;
3013
2787
  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
2788
  if (options.indentAllLines) {
3018
2789
  return;
3019
2790
  }
3020
-
3021
- // otherwise we start the next line without indent
3022
- return this.once('line', () => {
2791
+ this.once('line', () => {
3023
2792
  this.document.x -= indent;
3024
2793
  this.lineWidth += indent;
3025
2794
  if (options.continued && !this.continuedX) {
3026
2795
  this.continuedX = this.indent;
3027
2796
  }
3028
2797
  if (!options.continued) {
3029
- return this.continuedX = 0;
2798
+ this.continuedX = 0;
3030
2799
  }
3031
2800
  });
3032
2801
  });
3033
-
3034
- // handle left aligning last lines of paragraphs
3035
2802
  this.on('lastLine', options => {
3036
2803
  const {
3037
2804
  align
@@ -3040,7 +2807,7 @@ class LineWrapper extends events.EventEmitter {
3040
2807
  options.align = 'left';
3041
2808
  }
3042
2809
  this.lastLine = true;
3043
- return this.once('line', () => {
2810
+ this.once('line', () => {
3044
2811
  this.document.y += options.paragraphGap || 0;
3045
2812
  options.align = align;
3046
2813
  return this.lastLine = false;
@@ -3048,7 +2815,7 @@ class LineWrapper extends events.EventEmitter {
3048
2815
  });
3049
2816
  }
3050
2817
  wordWidth(word) {
3051
- return this.document.widthOfString(word, this) + this.characterSpacing + this.wordSpacing;
2818
+ return PDFNumber(this.document.widthOfString(word, this) + this.characterSpacing + this.wordSpacing);
3052
2819
  }
3053
2820
  canFit(word, w) {
3054
2821
  if (word[word.length - 1] != SOFT_HYPHEN) {
@@ -3057,7 +2824,6 @@ class LineWrapper extends events.EventEmitter {
3057
2824
  return w + this.wordWidth(HYPHEN) <= this.spaceLeft;
3058
2825
  }
3059
2826
  eachWord(text, fn) {
3060
- // setup a unicode line breaker
3061
2827
  let bk;
3062
2828
  const breaker = new LineBreaker(text);
3063
2829
  let last = null;
@@ -3066,19 +2832,12 @@ class LineWrapper extends events.EventEmitter {
3066
2832
  var shouldContinue;
3067
2833
  let word = text.slice((last != null ? last.position : undefined) || 0, bk.position);
3068
2834
  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
2835
  if (w > this.lineWidth + this.continuedX) {
3073
- // make some fake break objects
3074
2836
  let lbk = last;
3075
2837
  const fbk = {};
3076
2838
  while (word.length) {
3077
- // fit as much of the word as possible into the space we have
3078
2839
  var l, mightGrow;
3079
2840
  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
2841
  l = Math.ceil(this.spaceLeft / (w / word.length));
3083
2842
  w = this.wordWidth(word.slice(0, l));
3084
2843
  mightGrow = w <= this.spaceLeft && l < word.length;
@@ -3086,7 +2845,6 @@ class LineWrapper extends events.EventEmitter {
3086
2845
  l = word.length;
3087
2846
  }
3088
2847
  let mustShrink = w > this.spaceLeft && l > 0;
3089
- // shrink or grow word as necessary after our near-guess above
3090
2848
  while (mustShrink || mightGrow) {
3091
2849
  if (mustShrink) {
3092
2850
  w = this.wordWidth(word.slice(0, --l));
@@ -3097,20 +2855,14 @@ class LineWrapper extends events.EventEmitter {
3097
2855
  mightGrow = w <= this.spaceLeft && l < word.length;
3098
2856
  }
3099
2857
  }
3100
-
3101
- // check for the edge case where a single character cannot fit into a line.
3102
2858
  if (l === 0 && this.spaceLeft === this.lineWidth) {
3103
2859
  l = 1;
3104
2860
  }
3105
-
3106
- // send a required break unless this is the last piece and a linebreak is not specified
3107
2861
  fbk.required = bk.required || l < word.length;
3108
2862
  shouldContinue = fn(word.slice(0, l), w, fbk, lbk);
3109
2863
  lbk = {
3110
2864
  required: false
3111
2865
  };
3112
-
3113
- // get the remaining piece of the word
3114
2866
  word = word.slice(l);
3115
2867
  w = this.wordWidth(word);
3116
2868
  if (shouldContinue === false) {
@@ -3118,7 +2870,6 @@ class LineWrapper extends events.EventEmitter {
3118
2870
  }
3119
2871
  }
3120
2872
  } else {
3121
- // otherwise just emit the break as it was given to us
3122
2873
  shouldContinue = fn(word, w, bk, last);
3123
2874
  }
3124
2875
  if (shouldContinue === false) {
@@ -3128,7 +2879,6 @@ class LineWrapper extends events.EventEmitter {
3128
2879
  }
3129
2880
  }
3130
2881
  wrap(text, options) {
3131
- // override options from previous continued fragments
3132
2882
  this.horizontalScaling = options.horizontalScaling || 100;
3133
2883
  if (options.indent != null) {
3134
2884
  this.indent = options.indent * this.horizontalScaling / 100;
@@ -3142,10 +2892,6 @@ class LineWrapper extends events.EventEmitter {
3142
2892
  if (options.ellipsis != null) {
3143
2893
  this.ellipsis = options.ellipsis;
3144
2894
  }
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
2895
  const nextY = this.document.y + this.document.currentLineHeight(true);
3150
2896
  if (this.document.y > this.maxY || nextY > this.maxY) {
3151
2897
  this.nextSection();
@@ -3156,7 +2902,7 @@ class LineWrapper extends events.EventEmitter {
3156
2902
  let lc = 0;
3157
2903
  let {
3158
2904
  y
3159
- } = this.document; // used to reset Y pos if options.continued (below)
2905
+ } = this.document;
3160
2906
  const emitLine = () => {
3161
2907
  options.textWidth = textWidth + this.wordSpacing * (wc - 1);
3162
2908
  options.wordCount = wc;
@@ -3179,23 +2925,17 @@ class LineWrapper extends events.EventEmitter {
3179
2925
  wc++;
3180
2926
  }
3181
2927
  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
2928
  const lh = this.document.currentLineHeight(true);
3185
- if (this.height != null && this.ellipsis && this.document.y + lh * 2 > this.maxY && this.column >= this.columns) {
2929
+ if (this.height != null && this.ellipsis && PDFNumber(this.document.y + lh * 2) > this.maxY && this.column >= this.columns) {
3186
2930
  if (this.ellipsis === true) {
3187
2931
  this.ellipsis = '…';
3188
- } // map default ellipsis character
2932
+ }
3189
2933
  buffer = buffer.replace(/\s+$/, '');
3190
2934
  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
2935
  while (buffer && textWidth > this.lineWidth) {
3195
2936
  buffer = buffer.slice(0, -1).replace(/\s+$/, '');
3196
2937
  textWidth = this.wordWidth(buffer + this.ellipsis);
3197
2938
  }
3198
- // need to add ellipsis only if there is enough space for it
3199
2939
  if (textWidth <= this.lineWidth) {
3200
2940
  buffer = buffer + this.ellipsis;
3201
2941
  }
@@ -3210,35 +2950,25 @@ class LineWrapper extends events.EventEmitter {
3210
2950
  }
3211
2951
  this.emit('lastLine', options, this);
3212
2952
  }
3213
-
3214
- // Previous entry is a soft hyphen - add visible hyphen.
3215
2953
  if (buffer[buffer.length - 1] == SOFT_HYPHEN) {
3216
2954
  buffer = buffer.slice(0, -1) + HYPHEN;
3217
2955
  this.spaceLeft -= this.wordWidth(HYPHEN);
3218
2956
  }
3219
2957
  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) {
2958
+ if (PDFNumber(this.document.y + lh) > this.maxY) {
3224
2959
  const shouldContinue = this.nextSection();
3225
-
3226
- // stop if we reached the maximum height
3227
2960
  if (!shouldContinue) {
3228
2961
  wc = 0;
3229
2962
  buffer = '';
3230
2963
  return false;
3231
2964
  }
3232
2965
  }
3233
-
3234
- // reset the space left and buffer
3235
2966
  if (bk.required) {
3236
2967
  this.spaceLeft = this.lineWidth;
3237
2968
  buffer = '';
3238
2969
  textWidth = 0;
3239
2970
  return wc = 0;
3240
2971
  } else {
3241
- // reset the space left and buffer
3242
2972
  this.spaceLeft = this.lineWidth - w;
3243
2973
  buffer = word;
3244
2974
  textWidth = w;
@@ -3253,25 +2983,19 @@ class LineWrapper extends events.EventEmitter {
3253
2983
  emitLine();
3254
2984
  }
3255
2985
  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
2986
  if (options.continued === true) {
3261
2987
  if (lc > 1) {
3262
2988
  this.continuedX = 0;
3263
2989
  }
3264
2990
  this.continuedX += options.textWidth || 0;
3265
- return this.document.y = y;
2991
+ this.document.y = y;
3266
2992
  } else {
3267
- return this.document.x = this.startX;
2993
+ this.document.x = this.startX;
3268
2994
  }
3269
2995
  }
3270
2996
  nextSection(options) {
3271
2997
  this.emit('sectionEnd', options, this);
3272
2998
  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
2999
  if (this.height != null) {
3276
3000
  return false;
3277
3001
  }
@@ -3300,10 +3024,9 @@ const {
3300
3024
  var TextMixin = {
3301
3025
  initText() {
3302
3026
  this._line = this._line.bind(this);
3303
- // Current coordinates
3304
3027
  this.x = 0;
3305
3028
  this.y = 0;
3306
- return this._lineGap = 0;
3029
+ this._lineGap = 0;
3307
3030
  },
3308
3031
  lineGap(_lineGap) {
3309
3032
  this._lineGap = _lineGap;
@@ -3325,11 +3048,7 @@ var TextMixin = {
3325
3048
  },
3326
3049
  _text(text, x, y, options, lineCallback) {
3327
3050
  options = this._initOptions(x, y, options);
3328
-
3329
- // Convert text to a string
3330
3051
  text = text == null ? '' : `${text}`;
3331
-
3332
- // if the wordSpacing option is specified, remove multiple consecutive spaces
3333
3052
  if (options.wordSpacing) {
3334
3053
  text = text.replace(/\s{2,}/g, ' ');
3335
3054
  }
@@ -3338,8 +3057,12 @@ var TextMixin = {
3338
3057
  options.structParent.add(this.struct(options.structType || 'P', [this.markStructureContent(options.structType || 'P')]));
3339
3058
  }
3340
3059
  };
3341
-
3342
- // word wrapping
3060
+ if (options.rotation !== 0) {
3061
+ this.save();
3062
+ this.rotate(-options.rotation, {
3063
+ origin: [this.x, this.y]
3064
+ });
3065
+ }
3343
3066
  if (options.width) {
3344
3067
  let wrapper = this._wrapper;
3345
3068
  if (!wrapper) {
@@ -3350,14 +3073,13 @@ var TextMixin = {
3350
3073
  this._wrapper = options.continued ? wrapper : null;
3351
3074
  this._textOptions = options.continued ? options : null;
3352
3075
  wrapper.wrap(text, options);
3353
-
3354
- // render paragraphs as single lines
3355
3076
  } else {
3356
3077
  for (let line of text.split('\n')) {
3357
3078
  addStructure();
3358
3079
  lineCallback(line, options);
3359
3080
  }
3360
3081
  }
3082
+ if (options.rotation !== 0) this.restore();
3361
3083
  return this;
3362
3084
  },
3363
3085
  text(text, x, y, options) {
@@ -3367,17 +3089,108 @@ var TextMixin = {
3367
3089
  const horizontalScaling = options.horizontalScaling || 100;
3368
3090
  return (this._font.widthOfString(string, this._fontSize, options.features) + (options.characterSpacing || 0) * (string.length - 1)) * horizontalScaling / 100;
3369
3091
  },
3092
+ boundsOfString(string, x, y, options) {
3093
+ options = this._initOptions(x, y, options);
3094
+ ({
3095
+ x,
3096
+ y
3097
+ } = this);
3098
+ const lineGap = options.lineGap ?? this._lineGap ?? 0;
3099
+ const lineHeight = this.currentLineHeight(true) + lineGap;
3100
+ let contentWidth = 0;
3101
+ string = String(string ?? '');
3102
+ if (options.wordSpacing) {
3103
+ string = string.replace(/\s{2,}/g, ' ');
3104
+ }
3105
+ if (options.width) {
3106
+ let wrapper = new LineWrapper(this, options);
3107
+ wrapper.on('line', (text, options) => {
3108
+ this.y += lineHeight;
3109
+ text = text.replace(/\n/g, '');
3110
+ if (text.length) {
3111
+ let wordSpacing = options.wordSpacing ?? 0;
3112
+ const characterSpacing = options.characterSpacing ?? 0;
3113
+ if (options.width && options.align === 'justify') {
3114
+ const words = text.trim().split(/\s+/);
3115
+ const textWidth = this.widthOfString(text.replace(/\s+/g, ''), options);
3116
+ const spaceWidth = this.widthOfString(' ') + characterSpacing;
3117
+ wordSpacing = Math.max(0, (options.lineWidth - textWidth) / Math.max(1, words.length - 1) - spaceWidth);
3118
+ }
3119
+ contentWidth = Math.max(contentWidth, options.textWidth + wordSpacing * (options.wordCount - 1) + characterSpacing * (text.length - 1));
3120
+ }
3121
+ });
3122
+ wrapper.wrap(string, options);
3123
+ } else {
3124
+ for (let line of string.split('\n')) {
3125
+ const lineWidth = this.widthOfString(line, options);
3126
+ this.y += lineHeight;
3127
+ contentWidth = Math.max(contentWidth, lineWidth);
3128
+ }
3129
+ }
3130
+ let contentHeight = this.y - y;
3131
+ if (options.height) contentHeight = Math.min(contentHeight, options.height);
3132
+ this.x = x;
3133
+ this.y = y;
3134
+ if (options.rotation === 0) {
3135
+ return {
3136
+ x,
3137
+ y,
3138
+ width: contentWidth,
3139
+ height: contentHeight
3140
+ };
3141
+ } else if (options.rotation === 90) {
3142
+ return {
3143
+ x: x,
3144
+ y: y - contentWidth,
3145
+ width: contentHeight,
3146
+ height: contentWidth
3147
+ };
3148
+ } else if (options.rotation === 180) {
3149
+ return {
3150
+ x: x - contentWidth,
3151
+ y: y - contentHeight,
3152
+ width: contentWidth,
3153
+ height: contentHeight
3154
+ };
3155
+ } else if (options.rotation === 270) {
3156
+ return {
3157
+ x: x - contentHeight,
3158
+ y: y,
3159
+ width: contentHeight,
3160
+ height: contentWidth
3161
+ };
3162
+ }
3163
+ const cos = cosine(options.rotation);
3164
+ const sin = sine(options.rotation);
3165
+ const x1 = x;
3166
+ const y1 = y;
3167
+ const x2 = x + contentWidth * cos;
3168
+ const y2 = y - contentWidth * sin;
3169
+ const x3 = x + contentWidth * cos + contentHeight * sin;
3170
+ const y3 = y - contentWidth * sin + contentHeight * cos;
3171
+ const x4 = x + contentHeight * sin;
3172
+ const y4 = y + contentHeight * cos;
3173
+ const xMin = Math.min(x1, x2, x3, x4);
3174
+ const xMax = Math.max(x1, x2, x3, x4);
3175
+ const yMin = Math.min(y1, y2, y3, y4);
3176
+ const yMax = Math.max(y1, y2, y3, y4);
3177
+ return {
3178
+ x: xMin,
3179
+ y: yMin,
3180
+ width: xMax - xMin,
3181
+ height: yMax - yMin
3182
+ };
3183
+ },
3370
3184
  heightOfString(text, options) {
3371
3185
  const {
3372
3186
  x,
3373
3187
  y
3374
3188
  } = this;
3375
3189
  options = this._initOptions(options);
3376
- options.height = Infinity; // don't break pages
3377
-
3190
+ options.height = Infinity;
3378
3191
  const lineGap = options.lineGap || this._lineGap || 0;
3379
3192
  this._text(text, this.x, this.y, options, () => {
3380
- return this.y += this.currentLineHeight(true) + lineGap;
3193
+ this.y += this.currentLineHeight(true) + lineGap;
3381
3194
  });
3382
3195
  const height = this.y - y;
3383
3196
  this.x = x;
@@ -3475,12 +3288,12 @@ var TextMixin = {
3475
3288
  wrapper.on('sectionStart', () => {
3476
3289
  const pos = indent + itemIndent * (level - 1);
3477
3290
  this.x += pos;
3478
- return wrapper.lineWidth -= pos;
3291
+ wrapper.lineWidth -= pos;
3479
3292
  });
3480
3293
  wrapper.on('sectionEnd', () => {
3481
3294
  const pos = indent + itemIndent * (level - 1);
3482
3295
  this.x -= pos;
3483
- return wrapper.lineWidth += pos;
3296
+ wrapper.lineWidth += pos;
3484
3297
  });
3485
3298
  wrapper.wrap(listItem, options);
3486
3299
  };
@@ -3494,11 +3307,7 @@ var TextMixin = {
3494
3307
  options = x;
3495
3308
  x = null;
3496
3309
  }
3497
-
3498
- // clone options object
3499
3310
  const result = Object.assign({}, options);
3500
-
3501
- // extend options with previous values for continued text
3502
3311
  if (this._textOptions) {
3503
3312
  for (let key in this._textOptions) {
3504
3313
  const val = this._textOptions[key];
@@ -3509,16 +3318,12 @@ var TextMixin = {
3509
3318
  }
3510
3319
  }
3511
3320
  }
3512
-
3513
- // Update the current position
3514
3321
  if (x != null) {
3515
3322
  this.x = x;
3516
3323
  }
3517
3324
  if (y != null) {
3518
3325
  this.y = y;
3519
3326
  }
3520
-
3521
- // wrap to margins if no x or y position passed
3522
3327
  if (result.lineBreak !== false) {
3523
3328
  if (result.width == null) {
3524
3329
  result.width = this.page.width - this.x - this.page.margins.right;
@@ -3530,17 +3335,18 @@ var TextMixin = {
3530
3335
  }
3531
3336
  if (result.columnGap == null) {
3532
3337
  result.columnGap = 18;
3533
- } // 1/4 inch
3534
-
3338
+ }
3339
+ result.rotation = Number(options.rotation ?? 0) % 360;
3340
+ if (result.rotation < 0) result.rotation += 360;
3535
3341
  return result;
3536
3342
  },
3537
3343
  _line(text, options = {}, wrapper) {
3538
3344
  this._fragment(text, this.x, this.y, options);
3539
3345
  const lineGap = options.lineGap || this._lineGap || 0;
3540
3346
  if (!wrapper) {
3541
- return this.x += this.widthOfString(text, options);
3347
+ this.x += this.widthOfString(text, options);
3542
3348
  } else {
3543
- return this.y += this.currentLineHeight(true) + lineGap;
3349
+ this.y += this.currentLineHeight(true) + lineGap;
3544
3350
  }
3545
3351
  },
3546
3352
  _fragment(text, x, y, options) {
@@ -3549,14 +3355,10 @@ var TextMixin = {
3549
3355
  if (text.length === 0) {
3550
3356
  return;
3551
3357
  }
3552
-
3553
- // handle options
3554
3358
  const align = options.align || 'left';
3555
3359
  let wordSpacing = options.wordSpacing || 0;
3556
3360
  const characterSpacing = options.characterSpacing || 0;
3557
3361
  const horizontalScaling = options.horizontalScaling || 100;
3558
-
3559
- // text alignments
3560
3362
  if (options.width) {
3561
3363
  switch (align) {
3562
3364
  case 'right':
@@ -3567,7 +3369,6 @@ var TextMixin = {
3567
3369
  x += options.lineWidth / 2 - options.textWidth / 2;
3568
3370
  break;
3569
3371
  case 'justify':
3570
- // calculate the word spacing value
3571
3372
  words = text.trim().split(/\s+/);
3572
3373
  textWidth = this.widthOfString(text.replace(/\s+/g, ''), options);
3573
3374
  var spaceWidth = this.widthOfString(' ') + characterSpacing;
@@ -3575,8 +3376,6 @@ var TextMixin = {
3575
3376
  break;
3576
3377
  }
3577
3378
  }
3578
-
3579
- // text baseline alignments based on http://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling
3580
3379
  if (typeof options.baseline === 'number') {
3581
3380
  dy = -options.baseline;
3582
3381
  } else {
@@ -3609,11 +3408,7 @@ var TextMixin = {
3609
3408
  }
3610
3409
  dy = dy / 1000 * this._fontSize;
3611
3410
  }
3612
-
3613
- // calculate the actual rendered width of the string after word and character spacing
3614
3411
  const renderedWidth = options.textWidth + wordSpacing * (options.wordCount - 1) + characterSpacing * (text.length - 1);
3615
-
3616
- // create link annotations if the link option is given
3617
3412
  if (options.link != null) {
3618
3413
  this.link(x, y, renderedWidth, this.currentLineHeight(), options.link);
3619
3414
  }
@@ -3623,8 +3418,6 @@ var TextMixin = {
3623
3418
  if (options.destination != null) {
3624
3419
  this.addNamedDestination(options.destination, 'XYZ', x, y, null);
3625
3420
  }
3626
-
3627
- // create underline
3628
3421
  if (options.underline) {
3629
3422
  this.save();
3630
3423
  if (!options.stroke) {
@@ -3638,8 +3431,6 @@ var TextMixin = {
3638
3431
  this.stroke();
3639
3432
  this.restore();
3640
3433
  }
3641
-
3642
- // create strikethrough line
3643
3434
  if (options.strike) {
3644
3435
  this.save();
3645
3436
  if (!options.stroke) {
@@ -3654,8 +3445,6 @@ var TextMixin = {
3654
3445
  this.restore();
3655
3446
  }
3656
3447
  this.save();
3657
-
3658
- // oblique (angle in degrees or boolean)
3659
3448
  if (options.oblique) {
3660
3449
  let skew;
3661
3450
  if (typeof options.oblique === 'number') {
@@ -3667,45 +3456,24 @@ var TextMixin = {
3667
3456
  this.transform(1, 0, skew, 1, -skew * dy, 0);
3668
3457
  this.transform(1, 0, 0, 1, -x, -y);
3669
3458
  }
3670
-
3671
- // flip coordinate system
3672
3459
  this.transform(1, 0, 0, -1, 0, this.page.height);
3673
3460
  y = this.page.height - y - dy;
3674
-
3675
- // add current font to page if necessary
3676
3461
  if (this.page.fonts[this._font.id] == null) {
3677
3462
  this.page.fonts[this._font.id] = this._font.ref();
3678
3463
  }
3679
-
3680
- // begin the text object
3681
3464
  this.addContent('BT');
3682
-
3683
- // text position
3684
3465
  this.addContent(`1 0 0 1 ${number(x)} ${number(y)} Tm`);
3685
-
3686
- // font and font size
3687
3466
  this.addContent(`/${this._font.id} ${number(this._fontSize)} Tf`);
3688
-
3689
- // rendering mode
3690
3467
  const mode = options.fill && options.stroke ? 2 : options.stroke ? 1 : 0;
3691
3468
  if (mode) {
3692
3469
  this.addContent(`${mode} Tr`);
3693
3470
  }
3694
-
3695
- // Character spacing
3696
3471
  if (characterSpacing) {
3697
3472
  this.addContent(`${number(characterSpacing)} Tc`);
3698
3473
  }
3699
-
3700
- // Horizontal scaling
3701
3474
  if (horizontalScaling !== 100) {
3702
3475
  this.addContent(`${horizontalScaling} Tz`);
3703
3476
  }
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
3477
  if (wordSpacing) {
3710
3478
  words = text.trim().split(/\s+/);
3711
3479
  wordSpacing += this.widthOfString(' ') + characterSpacing;
@@ -3716,9 +3484,6 @@ var TextMixin = {
3716
3484
  const [encodedWord, positionsWord] = this._font.encode(word, options.features);
3717
3485
  encoded = encoded.concat(encodedWord);
3718
3486
  positions = positions.concat(positionsWord);
3719
-
3720
- // add the word spacing to the end of the word
3721
- // clone object because of cache
3722
3487
  const space = {};
3723
3488
  const object = positions[positions.length - 1];
3724
3489
  for (let key in object) {
@@ -3735,60 +3500,42 @@ var TextMixin = {
3735
3500
  const commands = [];
3736
3501
  let last = 0;
3737
3502
  let hadOffset = false;
3738
-
3739
- // Adds a segment of text to the TJ command buffer
3740
3503
  const addSegment = cur => {
3741
3504
  if (last < cur) {
3742
3505
  const hex = encoded.slice(last, cur).join('');
3743
3506
  const advance = positions[cur - 1].xAdvance - positions[cur - 1].advanceWidth;
3744
3507
  commands.push(`<${hex}> ${number(-advance)}`);
3745
3508
  }
3746
- return last = cur;
3509
+ last = cur;
3747
3510
  };
3748
-
3749
- // Flushes the current TJ commands to the output stream
3750
3511
  const flush = i => {
3751
3512
  addSegment(i);
3752
3513
  if (commands.length > 0) {
3753
3514
  this.addContent(`[${commands.join(' ')}] TJ`);
3754
- return commands.length = 0;
3515
+ commands.length = 0;
3755
3516
  }
3756
3517
  };
3757
3518
  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
3519
  const pos = positions[i];
3761
3520
  if (pos.xOffset || pos.yOffset) {
3762
- // Flush the current buffer
3763
3521
  flush(i);
3764
-
3765
- // Move the text position and flush just the current character
3766
3522
  this.addContent(`1 0 0 1 ${number(x + pos.xOffset * scale)} ${number(y + pos.yOffset * scale)} Tm`);
3767
3523
  flush(i + 1);
3768
3524
  hadOffset = true;
3769
3525
  } else {
3770
- // If the last character had an offset, reset the text position
3771
3526
  if (hadOffset) {
3772
3527
  this.addContent(`1 0 0 1 ${number(x)} ${number(y)} Tm`);
3773
3528
  hadOffset = false;
3774
3529
  }
3775
-
3776
- // Group segments that don't have any advance adjustments
3777
3530
  if (pos.xAdvance - pos.advanceWidth !== 0) {
3778
3531
  addSegment(i + 1);
3779
3532
  }
3780
3533
  }
3781
3534
  x += pos.xAdvance * scale;
3782
3535
  }
3783
-
3784
- // Flush any remaining commands
3785
3536
  flush(i);
3786
-
3787
- // end the text object
3788
3537
  this.addContent('ET');
3789
-
3790
- // restore flipped coordinate system
3791
- return this.restore();
3538
+ this.restore();
3792
3539
  }
3793
3540
  };
3794
3541
 
@@ -3806,8 +3553,6 @@ class JPEG {
3806
3553
  if (this.data.readUInt16BE(0) !== 0xffd8) {
3807
3554
  throw 'SOI not found in JPEG';
3808
3555
  }
3809
-
3810
- // Parse the EXIF orientation
3811
3556
  this.orientation = exif.fromBuffer(this.data).Orientation || 1;
3812
3557
  let pos = 2;
3813
3558
  while (pos < this.data.length) {
@@ -3844,16 +3589,10 @@ class JPEG {
3844
3589
  ColorSpace: this.colorSpace,
3845
3590
  Filter: 'DCTDecode'
3846
3591
  });
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
3592
  if (this.colorSpace === 'DeviceCMYK') {
3852
3593
  this.obj.data['Decode'] = [1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0];
3853
3594
  }
3854
3595
  this.obj.end(this.data);
3855
-
3856
- // free memory
3857
3596
  return this.data = null;
3858
3597
  }
3859
3598
  }
@@ -3896,24 +3635,14 @@ class PNGImage {
3896
3635
  if (this.image.palette.length === 0) {
3897
3636
  this.obj.data['ColorSpace'] = this.image.colorSpace;
3898
3637
  } else {
3899
- // embed the color palette in the PDF as an object stream
3900
3638
  const palette = this.document.ref();
3901
3639
  palette.end(Buffer.from(this.image.palette));
3902
-
3903
- // build the color space array for the image
3904
3640
  this.obj.data['ColorSpace'] = ['Indexed', 'DeviceRGB', this.image.palette.length / 3 - 1, palette];
3905
3641
  }
3906
-
3907
- // For PNG color types 0, 2 and 3, the transparency data is stored in
3908
- // a dedicated PNG chunk.
3909
3642
  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
3643
  const val = this.image.transparency.grayscale;
3913
3644
  this.obj.data['Mask'] = [val, val];
3914
3645
  } 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
3646
  const {
3918
3647
  rgb
3919
3648
  } = this.image.transparency;
@@ -3923,14 +3652,9 @@ class PNGImage {
3923
3652
  }
3924
3653
  this.obj.data['Mask'] = mask;
3925
3654
  } 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
3655
  dataDecoded = true;
3929
3656
  return this.loadIndexedAlphaChannel();
3930
3657
  } 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
3658
  dataDecoded = true;
3935
3659
  return this.splitAlphaChannel();
3936
3660
  }
@@ -3954,11 +3678,7 @@ class PNGImage {
3954
3678
  sMask.end(this.alphaChannel);
3955
3679
  this.obj.data['SMask'] = sMask;
3956
3680
  }
3957
-
3958
- // add the actual image data
3959
3681
  this.obj.end(this.imgData);
3960
-
3961
- // free memory
3962
3682
  this.image = null;
3963
3683
  return this.imgData = null;
3964
3684
  }
@@ -3971,7 +3691,6 @@ class PNGImage {
3971
3691
  const alphaChannel = Buffer.alloc(pixelCount);
3972
3692
  let i = p = a = 0;
3973
3693
  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
3694
  const skipByteCount = this.image.bits === 16 ? 1 : 0;
3976
3695
  while (i < len) {
3977
3696
  for (let colorIndex = 0; colorIndex < colorCount; colorIndex++) {
@@ -4006,10 +3725,6 @@ class PNGImage {
4006
3725
  }
4007
3726
  }
4008
3727
 
4009
- /*
4010
- PDFImage - embeds images in PDF documents
4011
- By Devon Govett
4012
- */
4013
3728
  class PDFImage {
4014
3729
  static open(src, label) {
4015
3730
  let data;
@@ -4018,8 +3733,8 @@ class PDFImage {
4018
3733
  } else if (src instanceof ArrayBuffer) {
4019
3734
  data = Buffer.from(new Uint8Array(src));
4020
3735
  } else {
4021
- let match;
4022
- if (match = /^data:.+?;base64,(.*)$/.exec(src)) {
3736
+ const match = /^data:.+?;base64,(.*)$/.exec(src);
3737
+ if (match) {
4023
3738
  data = Buffer.from(match[1], 'base64');
4024
3739
  } else {
4025
3740
  data = fs.readFileSync(src);
@@ -4041,17 +3756,16 @@ class PDFImage {
4041
3756
  var ImagesMixin = {
4042
3757
  initImages() {
4043
3758
  this._imageRegistry = {};
4044
- return this._imageCount = 0;
3759
+ this._imageCount = 0;
4045
3760
  },
4046
3761
  image(src, x, y, options = {}) {
4047
- let bh, bp, bw, image, ip, left, left1, rotateAngle, originX, originY;
3762
+ let bh, bp, bw, image, ip, left, left1, originX, originY;
4048
3763
  if (typeof x === 'object') {
4049
3764
  options = x;
4050
3765
  x = null;
4051
3766
  }
4052
-
4053
- // Ignore orientation based on document options or image options
4054
3767
  const ignoreOrientation = options.ignoreOrientation || options.ignoreOrientation !== false && this.options.ignoreOrientation;
3768
+ const inDocumentFlow = typeof y !== 'number';
4055
3769
  x = (left = x != null ? x : options.x) != null ? left : this.x;
4056
3770
  y = (left1 = y != null ? y : options.y) != null ? left1 : this.y;
4057
3771
  if (typeof src === 'string') {
@@ -4074,8 +3788,6 @@ var ImagesMixin = {
4074
3788
  width,
4075
3789
  height
4076
3790
  } = image;
4077
-
4078
- // If EXIF orientation calls for it, swap width and height
4079
3791
  if (!ignoreOrientation && image.orientation > 4) {
4080
3792
  [width, height] = [height, width];
4081
3793
  }
@@ -4127,80 +3839,70 @@ var ImagesMixin = {
4127
3839
  y = y + bh - h;
4128
3840
  }
4129
3841
  }
3842
+ let rotateAngle = 0;
3843
+ let xTransform = x;
3844
+ let yTransform = y;
3845
+ let hTransform = h;
3846
+ let wTransform = w;
4130
3847
  if (!ignoreOrientation) {
4131
3848
  switch (image.orientation) {
4132
- // No orientation (need to flip image, though, because of the default transform matrix on the document)
4133
3849
  default:
4134
3850
  case 1:
4135
- h = -h;
4136
- y -= h;
4137
- rotateAngle = 0;
3851
+ hTransform = -h;
3852
+ yTransform += h;
4138
3853
  break;
4139
- // Flip Horizontal
4140
3854
  case 2:
4141
- w = -w;
4142
- h = -h;
4143
- x -= w;
4144
- y -= h;
4145
- rotateAngle = 0;
3855
+ wTransform = -w;
3856
+ hTransform = -h;
3857
+ xTransform += w;
3858
+ yTransform += h;
4146
3859
  break;
4147
- // Rotate 180 degrees
4148
3860
  case 3:
4149
3861
  originX = x;
4150
3862
  originY = y;
4151
- h = -h;
4152
- x -= w;
3863
+ hTransform = -h;
3864
+ xTransform -= w;
4153
3865
  rotateAngle = 180;
4154
3866
  break;
4155
- // Flip vertical
4156
3867
  case 4:
4157
- // Do nothing, image will be flipped
4158
-
4159
3868
  break;
4160
- // Flip horizontally and rotate 270 degrees CW
4161
3869
  case 5:
4162
3870
  originX = x;
4163
3871
  originY = y;
4164
- [w, h] = [h, w];
4165
- y -= h;
3872
+ wTransform = h;
3873
+ hTransform = w;
3874
+ yTransform -= hTransform;
4166
3875
  rotateAngle = 90;
4167
3876
  break;
4168
- // Rotate 90 degrees CW
4169
3877
  case 6:
4170
3878
  originX = x;
4171
3879
  originY = y;
4172
- [w, h] = [h, w];
4173
- h = -h;
3880
+ wTransform = h;
3881
+ hTransform = -w;
4174
3882
  rotateAngle = 90;
4175
3883
  break;
4176
- // Flip horizontally and rotate 90 degrees CW
4177
3884
  case 7:
4178
3885
  originX = x;
4179
3886
  originY = y;
4180
- [w, h] = [h, w];
4181
- h = -h;
4182
- w = -w;
4183
- x -= w;
3887
+ hTransform = -w;
3888
+ wTransform = -h;
3889
+ xTransform += h;
4184
3890
  rotateAngle = 90;
4185
3891
  break;
4186
- // Rotate 270 degrees CW
4187
3892
  case 8:
4188
3893
  originX = x;
4189
3894
  originY = y;
4190
- [w, h] = [h, w];
4191
- h = -h;
4192
- x -= w;
4193
- y -= h;
3895
+ wTransform = h;
3896
+ hTransform = -w;
3897
+ xTransform -= h;
3898
+ yTransform += w;
4194
3899
  rotateAngle = -90;
4195
3900
  break;
4196
3901
  }
4197
3902
  } else {
4198
- h = -h;
4199
- y -= h;
4200
- rotateAngle = 0;
3903
+ hTransform = -h;
3904
+ yTransform += h;
4201
3905
  }
4202
-
4203
- // create link annotations if the link option is given
4204
3906
  if (options.link != null) {
4205
3907
  this.link(x, y, w, h, options.link);
4206
3908
  }
@@ -4210,9 +3912,7 @@ var ImagesMixin = {
4210
3912
  if (options.destination != null) {
4211
3913
  this.addNamedDestination(options.destination, 'XYZ', x, y, null);
4212
3914
  }
4213
-
4214
- // Set the current y position to below the image if it is in the document flow
4215
- if (this.y === y) {
3915
+ if (inDocumentFlow) {
4216
3916
  this.y += h;
4217
3917
  }
4218
3918
  this.save();
@@ -4221,7 +3921,7 @@ var ImagesMixin = {
4221
3921
  origin: [originX, originY]
4222
3922
  });
4223
3923
  }
4224
- this.transform(w, 0, 0, h, x, y);
3924
+ this.transform(wTransform, 0, 0, hTransform, xTransform, yTransform);
4225
3925
  this.addContent(`/${image.label} Do`);
4226
3926
  this.restore();
4227
3927
  return this;
@@ -4247,19 +3947,17 @@ var AnnotationsMixin = {
4247
3947
  options.Rect = this._convertRect(x, y, w, h);
4248
3948
  options.Border = [0, 0, 0];
4249
3949
  if (options.Subtype === 'Link' && typeof options.F === 'undefined') {
4250
- options.F = 1 << 2; // Print Annotation Flag
3950
+ options.F = 1 << 2;
4251
3951
  }
4252
3952
  if (options.Subtype !== 'Link') {
4253
3953
  if (options.C == null) {
4254
3954
  options.C = this._normalizeColor(options.color || [0, 0, 0]);
4255
3955
  }
4256
- } // convert colors
3956
+ }
4257
3957
  delete options.color;
4258
3958
  if (typeof options.Dest === 'string') {
4259
3959
  options.Dest = new String(options.Dest);
4260
3960
  }
4261
-
4262
- // Capitalize keys
4263
3961
  for (let key in options) {
4264
3962
  const val = options[key];
4265
3963
  options[key[0].toUpperCase() + key.slice(1)] = val;
@@ -4292,7 +3990,6 @@ var AnnotationsMixin = {
4292
3990
  link(x, y, w, h, url, options = {}) {
4293
3991
  options.Subtype = 'Link';
4294
3992
  if (typeof url === 'number') {
4295
- // Link to a page in the document (the page must already exist)
4296
3993
  const pages = this._root.data.Pages.data;
4297
3994
  if (url >= 0 && url < pages.Kids.length) {
4298
3995
  options.A = this.ref({
@@ -4304,7 +4001,6 @@ var AnnotationsMixin = {
4304
4001
  throw new Error(`The document has no page ${url}`);
4305
4002
  }
4306
4003
  } else {
4307
- // Link to an external url
4308
4004
  options.A = this.ref({
4309
4005
  S: 'URI',
4310
4006
  URI: new String(url)
@@ -4357,14 +4053,11 @@ var AnnotationsMixin = {
4357
4053
  return this.annotate(x, y, w, h, options);
4358
4054
  },
4359
4055
  fileAnnotation(x, y, w, h, file = {}, options = {}) {
4360
- // create hidden file
4361
4056
  const filespec = this.file(file.src, Object.assign({
4362
4057
  hidden: true
4363
4058
  }, file));
4364
4059
  options.Subtype = 'FileAttachment';
4365
4060
  options.FS = filespec;
4366
-
4367
- // add description from filespec unless description (Contents) has already been set
4368
4061
  if (options.Contents) {
4369
4062
  options.Contents = new String(options.Contents);
4370
4063
  } else if (filespec.data.Desc) {
@@ -4373,14 +4066,9 @@ var AnnotationsMixin = {
4373
4066
  return this.annotate(x, y, w, h, options);
4374
4067
  },
4375
4068
  _convertRect(x1, y1, w, h) {
4376
- // flip y1 and y2
4377
4069
  let y2 = y1;
4378
4070
  y1 += h;
4379
-
4380
- // make x2
4381
4071
  let x2 = x1 + w;
4382
-
4383
- // apply current transformation matrix to points
4384
4072
  const [m0, m1, m2, m3, m4, m5] = this._ctm;
4385
4073
  x1 = m0 * x1 + m2 * y1 + m4;
4386
4074
  y1 = m1 * x1 + m3 * y1 + m5;
@@ -4442,7 +4130,7 @@ class PDFOutline {
4442
4130
 
4443
4131
  var OutlineMixin = {
4444
4132
  initOutline() {
4445
- return this.outline = new PDFOutline(this, null, null, null);
4133
+ this.outline = new PDFOutline(this, null, null, null);
4446
4134
  },
4447
4135
  endOutline() {
4448
4136
  this.outline.endOutline();
@@ -4453,11 +4141,6 @@ var OutlineMixin = {
4453
4141
  }
4454
4142
  };
4455
4143
 
4456
- /*
4457
- PDFStructureContent - a reference to a marked structure content
4458
- By Ben Schmidt
4459
- */
4460
-
4461
4144
  class PDFStructureContent {
4462
4145
  constructor(pageRef, mcid) {
4463
4146
  this.refs = [{
@@ -4470,10 +4153,6 @@ class PDFStructureContent {
4470
4153
  }
4471
4154
  }
4472
4155
 
4473
- /*
4474
- PDFStructureElement - represents an element in the PDF logical structure tree
4475
- By Ben Schmidt
4476
- */
4477
4156
  class PDFStructureElement {
4478
4157
  constructor(document, type, options = {}, children = null) {
4479
4158
  this.document = document;
@@ -4481,7 +4160,6 @@ class PDFStructureElement {
4481
4160
  this._ended = false;
4482
4161
  this._flushed = false;
4483
4162
  this.dictionary = document.ref({
4484
- // Type: "StructElem",
4485
4163
  S: type
4486
4164
  });
4487
4165
  const data = this.dictionary.data;
@@ -4530,7 +4208,6 @@ class PDFStructureElement {
4530
4208
  this._addContentToParentTree(child);
4531
4209
  }
4532
4210
  if (typeof child === 'function' && this._attached) {
4533
- // _contentForClosure() adds the content to the parent tree
4534
4211
  child = this._contentForClosure(child);
4535
4212
  }
4536
4213
  this._children.push(child);
@@ -4606,10 +4283,6 @@ class PDFStructureElement {
4606
4283
  this.dictionary.data.K = [];
4607
4284
  this._children.forEach(child => this._flushChild(child));
4608
4285
  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
4286
  this._children = [];
4614
4287
  this.dictionary.data.K = null;
4615
4288
  this._flushed = true;
@@ -4630,7 +4303,7 @@ class PDFStructureElement {
4630
4303
  this.dictionary.data.K.push(mcid);
4631
4304
  } else {
4632
4305
  this.dictionary.data.K.push({
4633
- Type: "MCR",
4306
+ Type: 'MCR',
4634
4307
  Pg: pageRef,
4635
4308
  MCID: mcid
4636
4309
  });
@@ -4640,25 +4313,18 @@ class PDFStructureElement {
4640
4313
  }
4641
4314
  }
4642
4315
 
4643
- /*
4644
- PDFNumberTree - represents a number tree object
4645
- */
4646
4316
  class PDFNumberTree extends PDFTree {
4647
4317
  _compareKeys(a, b) {
4648
4318
  return parseInt(a) - parseInt(b);
4649
4319
  }
4650
4320
  _keysName() {
4651
- return "Nums";
4321
+ return 'Nums';
4652
4322
  }
4653
4323
  _dataForKey(k) {
4654
4324
  return parseInt(k);
4655
4325
  }
4656
4326
  }
4657
4327
 
4658
- /*
4659
- Markings mixin - support marked content sequences in content streams
4660
- By Ben Schmidt
4661
- */
4662
4328
  var MarkingsMixin = {
4663
4329
  initMarkings(options) {
4664
4330
  this.structChildren = [];
@@ -4794,7 +4460,6 @@ var MarkingsMixin = {
4794
4460
  return this.getStructTreeRoot().data.ParentTree;
4795
4461
  },
4796
4462
  createStructParentTreeNextKey() {
4797
- // initialise the MarkInfo dictionary
4798
4463
  this.getMarkInfoDictionary();
4799
4464
  const structTreeRoot = this.getStructTreeRoot();
4800
4465
  const key = structTreeRoot.data.ParentTreeNextKey++;
@@ -4858,10 +4523,6 @@ const FORMAT_DEFAULT = {
4858
4523
  }
4859
4524
  };
4860
4525
  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
4526
  initForm() {
4866
4527
  if (!this._font) {
4867
4528
  throw new Error('Must set a font before calling initForm method');
@@ -4884,9 +4545,6 @@ var AcroFormMixin = {
4884
4545
  this._root.data.AcroForm = AcroForm;
4885
4546
  return this;
4886
4547
  },
4887
- /**
4888
- * Called automatically by document.js
4889
- */
4890
4548
  endAcroForm() {
4891
4549
  if (this._root.data.AcroForm) {
4892
4550
  if (!Object.keys(this._acroform.fonts).length && !this._acroform.defaultFont) {
@@ -4912,38 +4570,18 @@ var AcroFormMixin = {
4912
4570
  }
4913
4571
  return this;
4914
4572
  },
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
4573
  formField(name, options = {}) {
4923
4574
  let fieldDict = this._fieldDict(name, null, options);
4924
4575
  let fieldRef = this.ref(fieldDict);
4925
4576
  this._addToParent(fieldRef);
4926
4577
  return fieldRef;
4927
4578
  },
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
4579
  formAnnotation(name, type, x, y, w, h, options = {}) {
4940
4580
  let fieldDict = this._fieldDict(name, type, options);
4941
4581
  fieldDict.Subtype = 'Widget';
4942
4582
  if (fieldDict.F === undefined) {
4943
- fieldDict.F = 4; // print the annotation
4583
+ fieldDict.F = 4;
4944
4584
  }
4945
-
4946
- // Add Field annot to page, and get it's ref
4947
4585
  this.annotate(x, y, w, h, fieldDict);
4948
4586
  let annotRef = this.page.annotations[this.page.annotations.length - 1];
4949
4587
  return this._addToParent(annotRef);
@@ -5104,23 +4742,18 @@ var AcroFormMixin = {
5104
4742
  delete options.align;
5105
4743
  }
5106
4744
  if (result !== 0) {
5107
- options.Q = result; // default
4745
+ options.Q = result;
5108
4746
  }
5109
4747
  return options;
5110
4748
  },
5111
4749
  _resolveFont(options) {
5112
- // add current font to document-level AcroForm dict if necessary
5113
4750
  if (this._acroform.fonts[this._font.id] == null) {
5114
4751
  this._acroform.fonts[this._font.id] = this._font.ref();
5115
4752
  }
5116
-
5117
- // add current font to field's resource dict (RD) if not the default acroform font
5118
4753
  if (this._acroform.defaultFont !== this._font.name) {
5119
4754
  options.DR = {
5120
4755
  Font: {}
5121
4756
  };
5122
-
5123
- // Get the fontSize option. If not set use auto sizing
5124
4757
  const fontSize = options.fontSize || 0;
5125
4758
  options.DR.Font[this._font.id] = this._font.ref();
5126
4759
  options.DA = new String(`/${this._font.id} ${fontSize} Tf 0 g`);
@@ -5172,19 +4805,6 @@ var AcroFormMixin = {
5172
4805
  };
5173
4806
 
5174
4807
  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
4808
  file(src, options = {}) {
5189
4809
  options.name = options.name || src;
5190
4810
  options.relationship = options.relationship || 'Unspecified';
@@ -5201,8 +4821,8 @@ var AttachmentsMixin = {
5201
4821
  } else if (src instanceof ArrayBuffer) {
5202
4822
  data = Buffer.from(new Uint8Array(src));
5203
4823
  } else {
5204
- let match;
5205
- if (match = /^data:(.*?);base64,(.*)$/.exec(src)) {
4824
+ const match = /^data:(.*?);base64,(.*)$/.exec(src);
4825
+ if (match) {
5206
4826
  if (match[1]) {
5207
4827
  refBody.Subtype = match[1].replace('/', '#2F');
5208
4828
  }
@@ -5212,8 +4832,6 @@ var AttachmentsMixin = {
5212
4832
  if (!data) {
5213
4833
  throw new Error(`Could not read contents of file at filepath ${src}`);
5214
4834
  }
5215
-
5216
- // update CreationDate and ModDate
5217
4835
  const {
5218
4836
  birthtime,
5219
4837
  ctime
@@ -5222,26 +4840,18 @@ var AttachmentsMixin = {
5222
4840
  refBody.Params.ModDate = ctime;
5223
4841
  }
5224
4842
  }
5225
-
5226
- // override creation date and modified date
5227
4843
  if (options.creationDate instanceof Date) {
5228
4844
  refBody.Params.CreationDate = options.creationDate;
5229
4845
  }
5230
4846
  if (options.modifiedDate instanceof Date) {
5231
4847
  refBody.Params.ModDate = options.modifiedDate;
5232
4848
  }
5233
- // add optional subtype
5234
4849
  if (options.type) {
5235
4850
  refBody.Subtype = options.type.replace('/', '#2F');
5236
4851
  }
5237
-
5238
- // add checksum and size information
5239
4852
  const checksum = CryptoJS.MD5(CryptoJS.lib.WordArray.create(new Uint8Array(data)));
5240
4853
  refBody.Params.CheckSum = new String(checksum);
5241
4854
  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
4855
  let ref;
5246
4856
  if (!this._fileRegistry) this._fileRegistry = {};
5247
4857
  let file = this._fileRegistry[options.name];
@@ -5255,7 +4865,6 @@ var AttachmentsMixin = {
5255
4865
  ref
5256
4866
  };
5257
4867
  }
5258
- // add filespec for embedded file
5259
4868
  const fileSpecBody = {
5260
4869
  Type: 'Filespec',
5261
4870
  AFRelationship: options.relationship,
@@ -5273,8 +4882,6 @@ var AttachmentsMixin = {
5273
4882
  if (!options.hidden) {
5274
4883
  this.addNamedEmbeddedFile(options.name, filespec);
5275
4884
  }
5276
-
5277
- // Add file to the catalogue to be PDF/A3 compliant
5278
4885
  if (this._root.data.AF) {
5279
4886
  this._root.data.AF.push(filespec);
5280
4887
  } else {
@@ -5283,8 +4890,6 @@ var AttachmentsMixin = {
5283
4890
  return filespec;
5284
4891
  }
5285
4892
  };
5286
-
5287
- /** check two embedded file metadata objects for equality */
5288
4893
  function isEqual(a, b) {
5289
4894
  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
4895
  }
@@ -5295,7 +4900,6 @@ var PDFA = {
5295
4900
  this.subset_conformance = pSubset.charAt(pSubset.length - 1).toUpperCase();
5296
4901
  this.subset = parseInt(pSubset.charAt(pSubset.length - 2));
5297
4902
  } else {
5298
- // Default to Basic conformance when user doesn't specify
5299
4903
  this.subset_conformance = 'B';
5300
4904
  this.subset = parseInt(pSubset.charAt(pSubset.length - 1));
5301
4905
  }
@@ -5380,6 +4984,642 @@ var SubsetMixin = {
5380
4984
  }
5381
4985
  };
5382
4986
 
4987
+ const ROW_FIELDS = ['height', 'minHeight', 'maxHeight'];
4988
+ const COLUMN_FIELDS = ['width', 'minWidth', 'maxWidth'];
4989
+ function memoize(fn, maxSize) {
4990
+ const cache = new Map();
4991
+ return function (...args) {
4992
+ const key = args[0];
4993
+ if (!cache.has(key)) {
4994
+ cache.set(key, fn(...args));
4995
+ if (cache.size > maxSize) cache.delete(cache.keys().next());
4996
+ }
4997
+ return cache.get(key);
4998
+ };
4999
+ }
5000
+ function isObject(item) {
5001
+ return item && typeof item === 'object' && !Array.isArray(item);
5002
+ }
5003
+ function deepMerge(target, ...sources) {
5004
+ if (!isObject(target)) return target;
5005
+ target = deepClone(target);
5006
+ for (const source of sources) {
5007
+ if (isObject(source)) {
5008
+ for (const key in source) {
5009
+ if (isObject(source[key])) {
5010
+ if (!(key in target)) target[key] = {};
5011
+ target[key] = deepMerge(target[key], source[key]);
5012
+ } else if (source[key] !== undefined) {
5013
+ target[key] = deepClone(source[key]);
5014
+ }
5015
+ }
5016
+ }
5017
+ }
5018
+ return target;
5019
+ }
5020
+ function deepClone(obj) {
5021
+ let result = obj;
5022
+ if (obj && typeof obj == 'object') {
5023
+ result = Array.isArray(obj) ? [] : {};
5024
+ for (const key in obj) result[key] = deepClone(obj[key]);
5025
+ }
5026
+ return result;
5027
+ }
5028
+
5029
+ function normalizedDefaultStyle(defaultStyleInternal) {
5030
+ let defaultStyle = defaultStyleInternal;
5031
+ if (typeof defaultStyle !== 'object') defaultStyle = {
5032
+ text: defaultStyle
5033
+ };
5034
+ const defaultRowStyle = Object.fromEntries(Object.entries(defaultStyle).filter(([k]) => ROW_FIELDS.includes(k)));
5035
+ const defaultColStyle = Object.fromEntries(Object.entries(defaultStyle).filter(([k]) => COLUMN_FIELDS.includes(k)));
5036
+ defaultStyle.padding = normalizeSides(defaultStyle.padding);
5037
+ defaultStyle.border = normalizeSides(defaultStyle.border);
5038
+ defaultStyle.borderColor = normalizeSides(defaultStyle.borderColor);
5039
+ defaultStyle.align = normalizeAlignment(defaultStyle.align);
5040
+ return {
5041
+ defaultStyle,
5042
+ defaultRowStyle,
5043
+ defaultColStyle
5044
+ };
5045
+ }
5046
+ function normalizedRowStyle(defaultRowStyle, rowStyleInternal, i) {
5047
+ let rowStyle = rowStyleInternal(i);
5048
+ if (rowStyle == null || typeof rowStyle !== 'object') {
5049
+ rowStyle = {
5050
+ height: rowStyle
5051
+ };
5052
+ }
5053
+ rowStyle.padding = normalizeSides(rowStyle.padding);
5054
+ rowStyle.border = normalizeSides(rowStyle.border);
5055
+ rowStyle.borderColor = normalizeSides(rowStyle.borderColor);
5056
+ rowStyle.align = normalizeAlignment(rowStyle.align);
5057
+ rowStyle = deepMerge(defaultRowStyle, rowStyle);
5058
+ const document = this.document;
5059
+ const page = document.page;
5060
+ const contentHeight = page.contentHeight;
5061
+ if (rowStyle.height == null || rowStyle.height === 'auto') {
5062
+ rowStyle.height = 'auto';
5063
+ } else {
5064
+ rowStyle.height = document.sizeToPoint(rowStyle.height, 0, page, contentHeight);
5065
+ }
5066
+ rowStyle.minHeight = document.sizeToPoint(rowStyle.minHeight, 0, page, contentHeight);
5067
+ rowStyle.maxHeight = document.sizeToPoint(rowStyle.maxHeight, 0, page, contentHeight);
5068
+ return rowStyle;
5069
+ }
5070
+ function normalizedColumnStyle(defaultColStyle, colStyleInternal, i) {
5071
+ let colStyle = colStyleInternal(i);
5072
+ if (colStyle == null || typeof colStyle !== 'object') {
5073
+ colStyle = {
5074
+ width: colStyle
5075
+ };
5076
+ }
5077
+ colStyle.padding = normalizeSides(colStyle.padding);
5078
+ colStyle.border = normalizeSides(colStyle.border);
5079
+ colStyle.borderColor = normalizeSides(colStyle.borderColor);
5080
+ colStyle.align = normalizeAlignment(colStyle.align);
5081
+ colStyle = deepMerge(defaultColStyle, colStyle);
5082
+ if (colStyle.width == null || colStyle.width === '*') {
5083
+ colStyle.width = '*';
5084
+ } else {
5085
+ colStyle.width = this.document.sizeToPoint(colStyle.width, 0, this.document.page, this._maxWidth);
5086
+ }
5087
+ colStyle.minWidth = this.document.sizeToPoint(colStyle.minWidth, 0, this.document.page, this._maxWidth);
5088
+ colStyle.maxWidth = this.document.sizeToPoint(colStyle.maxWidth, 0, this.document.page, this._maxWidth);
5089
+ return colStyle;
5090
+ }
5091
+ function normalizeAlignment(align) {
5092
+ return align == null || typeof align === 'string' ? {
5093
+ x: align,
5094
+ y: align
5095
+ } : align;
5096
+ }
5097
+
5098
+ function normalizeTable() {
5099
+ const doc = this.document;
5100
+ const opts = this.opts;
5101
+ let index = doc._tableIndex++;
5102
+ this._id = new String(opts.id ?? `table-${index}`);
5103
+ this._position = {
5104
+ x: doc.sizeToPoint(opts.position?.x, doc.x),
5105
+ y: doc.sizeToPoint(opts.position?.y, doc.y)
5106
+ };
5107
+ this._maxWidth = doc.sizeToPoint(opts.maxWidth, doc.page.width - doc.page.margins.right - this._position.x);
5108
+ const {
5109
+ defaultStyle,
5110
+ defaultColStyle,
5111
+ defaultRowStyle
5112
+ } = normalizedDefaultStyle(opts.defaultStyle);
5113
+ this._defaultStyle = defaultStyle;
5114
+ let colStyle;
5115
+ if (opts.columnStyles) {
5116
+ if (Array.isArray(opts.columnStyles)) {
5117
+ colStyle = i => opts.columnStyles[i];
5118
+ } else if (typeof opts.columnStyles === 'function') {
5119
+ colStyle = memoize(i => opts.columnStyles(i), Infinity);
5120
+ } else if (typeof opts.columnStyles === 'object') {
5121
+ colStyle = () => opts.columnStyles;
5122
+ }
5123
+ }
5124
+ if (!colStyle) colStyle = () => ({});
5125
+ this._colStyle = normalizedColumnStyle.bind(this, defaultColStyle, colStyle);
5126
+ let rowStyle;
5127
+ if (opts.rowStyles) {
5128
+ if (Array.isArray(opts.rowStyles)) {
5129
+ rowStyle = i => opts.rowStyles[i];
5130
+ } else if (typeof opts.rowStyles === 'function') {
5131
+ rowStyle = memoize(i => opts.rowStyles(i), 10);
5132
+ } else if (typeof opts.rowStyles === 'object') {
5133
+ rowStyle = () => opts.rowStyles;
5134
+ }
5135
+ }
5136
+ if (!rowStyle) rowStyle = () => ({});
5137
+ this._rowStyle = normalizedRowStyle.bind(this, defaultRowStyle, rowStyle);
5138
+ }
5139
+ function normalizeText(text) {
5140
+ if (text != null) text = `${text}`;
5141
+ return text;
5142
+ }
5143
+ function normalizeCell(cell, rowIndex, colIndex) {
5144
+ const colStyle = this._colStyle(colIndex);
5145
+ let rowStyle = this._rowStyle(rowIndex);
5146
+ const font = deepMerge({}, colStyle.font, rowStyle.font, cell.font);
5147
+ const customFont = Object.values(font).filter(v => v != null).length > 0;
5148
+ const doc = this.document;
5149
+ const rollbackFont = doc._fontSource;
5150
+ const rollbackFontSize = doc._fontSize;
5151
+ const rollbackFontFamily = doc._fontFamily;
5152
+ if (customFont) {
5153
+ if (font.src) doc.font(font.src, font.family);
5154
+ if (font.size) doc.fontSize(font.size);
5155
+ rowStyle = this._rowStyle(rowIndex);
5156
+ }
5157
+ cell.padding = normalizeSides(cell.padding);
5158
+ cell.border = normalizeSides(cell.border);
5159
+ cell.borderColor = normalizeSides(cell.borderColor);
5160
+ const config = deepMerge(this._defaultStyle, colStyle, rowStyle, cell);
5161
+ config.rowIndex = rowIndex;
5162
+ config.colIndex = colIndex;
5163
+ config.font = font ?? {};
5164
+ config.customFont = customFont;
5165
+ config.text = normalizeText(config.text);
5166
+ config.rowSpan = config.rowSpan ?? 1;
5167
+ config.colSpan = config.colSpan ?? 1;
5168
+ config.padding = normalizeSides(config.padding, '0.25em', x => doc.sizeToPoint(x, '0.25em'));
5169
+ config.border = normalizeSides(config.border, 1, x => doc.sizeToPoint(x, 1));
5170
+ config.borderColor = normalizeSides(config.borderColor, 'black', x => x ?? 'black');
5171
+ config.align = normalizeAlignment(config.align);
5172
+ config.align.x = config.align.x ?? 'left';
5173
+ config.align.y = config.align.y ?? 'top';
5174
+ config.textStroke = doc.sizeToPoint(config.textStroke, 0);
5175
+ config.textStrokeColor = config.textStrokeColor ?? 'black';
5176
+ config.textColor = config.textColor ?? 'black';
5177
+ config.textOptions = config.textOptions ?? {};
5178
+ config.id = new String(config.id ?? `${this._id}-${rowIndex}-${colIndex}`);
5179
+ config.type = config.type?.toUpperCase() === 'TH' ? 'TH' : 'TD';
5180
+ if (config.scope) {
5181
+ config.scope = config.scope.toLowerCase();
5182
+ if (config.scope === 'row') config.scope = 'Row';else if (config.scope === 'both') config.scope = 'Both';else if (config.scope === 'column') config.scope = 'Column';
5183
+ }
5184
+ if (typeof this.opts.debug === 'boolean') config.debug = this.opts.debug;
5185
+ if (customFont) doc.font(rollbackFont, rollbackFontFamily, rollbackFontSize);
5186
+ return config;
5187
+ }
5188
+ function normalizeRow(row, rowIndex) {
5189
+ if (!this._cellClaim) this._cellClaim = new Set();
5190
+ let colIndex = 0;
5191
+ return row.map(cell => {
5192
+ if (cell == null || typeof cell !== 'object') cell = {
5193
+ text: cell
5194
+ };
5195
+ while (this._cellClaim.has(`${rowIndex},${colIndex}`)) {
5196
+ colIndex++;
5197
+ }
5198
+ cell = normalizeCell.call(this, cell, rowIndex, colIndex);
5199
+ for (let i = 0; i < cell.rowSpan; i++) {
5200
+ for (let j = 0; j < cell.colSpan; j++) {
5201
+ this._cellClaim.add(`${rowIndex + i},${colIndex + j}`);
5202
+ }
5203
+ }
5204
+ colIndex += cell.colSpan;
5205
+ return cell;
5206
+ });
5207
+ }
5208
+
5209
+ function ensure(row) {
5210
+ this._columnWidths = [];
5211
+ ensureColumnWidths.call(this, row.reduce((a, cell) => a + cell.colSpan, 0));
5212
+ this._rowHeights = [];
5213
+ this._rowYPos = [this._position.y];
5214
+ this._rowBuffer = new Set();
5215
+ }
5216
+ function ensureColumnWidths(numCols) {
5217
+ let starColumnIndexes = [];
5218
+ let starMinAcc = 0;
5219
+ let unclaimedWidth = this._maxWidth;
5220
+ for (let i = 0; i < numCols; i++) {
5221
+ let col = this._colStyle(i);
5222
+ if (col.width === '*') {
5223
+ starColumnIndexes[i] = col;
5224
+ starMinAcc += col.minWidth;
5225
+ } else {
5226
+ unclaimedWidth -= col.width;
5227
+ this._columnWidths[i] = col.width;
5228
+ }
5229
+ }
5230
+ let starColCount = starColumnIndexes.reduce(x => x + 1, 0);
5231
+ if (starMinAcc >= unclaimedWidth) {
5232
+ starColumnIndexes.forEach((cell, i) => {
5233
+ this._columnWidths[i] = cell.minWidth;
5234
+ });
5235
+ } else if (starColCount > 0) {
5236
+ starColumnIndexes.forEach((col, i) => {
5237
+ let starSize = unclaimedWidth / starColCount;
5238
+ this._columnWidths[i] = Math.max(starSize, col.minWidth);
5239
+ if (col.maxWidth > 0) {
5240
+ this._columnWidths[i] = Math.min(this._columnWidths[i], col.maxWidth);
5241
+ }
5242
+ unclaimedWidth -= this._columnWidths[i];
5243
+ starColCount--;
5244
+ });
5245
+ }
5246
+ let tempX = this._position.x;
5247
+ this._columnXPos = Array.from(this._columnWidths, v => {
5248
+ const t = tempX;
5249
+ tempX += v;
5250
+ return t;
5251
+ });
5252
+ }
5253
+ function measure(row, rowIndex) {
5254
+ row.forEach(cell => this._rowBuffer.add(cell));
5255
+ if (rowIndex > 0) {
5256
+ this._rowYPos[rowIndex] = this._rowYPos[rowIndex - 1] + this._rowHeights[rowIndex - 1];
5257
+ }
5258
+ const rowStyle = this._rowStyle(rowIndex);
5259
+ let toRender = [];
5260
+ this._rowBuffer.forEach(cell => {
5261
+ if (cell.rowIndex + cell.rowSpan - 1 === rowIndex) {
5262
+ toRender.push(measureCell.call(this, cell, rowStyle.height));
5263
+ this._rowBuffer.delete(cell);
5264
+ }
5265
+ });
5266
+ let rowHeight = rowStyle.height;
5267
+ if (rowHeight === 'auto') {
5268
+ rowHeight = toRender.reduce((acc, cell) => {
5269
+ let minHeight = cell.textBounds.height + cell.padding.top + cell.padding.bottom;
5270
+ for (let i = 0; i < cell.rowSpan - 1; i++) {
5271
+ minHeight -= this._rowHeights[cell.rowIndex + i];
5272
+ }
5273
+ return Math.max(acc, minHeight);
5274
+ }, 0);
5275
+ }
5276
+ rowHeight = Math.max(rowHeight, rowStyle.minHeight);
5277
+ if (rowStyle.maxHeight > 0) {
5278
+ rowHeight = Math.min(rowHeight, rowStyle.maxHeight);
5279
+ }
5280
+ this._rowHeights[rowIndex] = rowHeight;
5281
+ let newPage = false;
5282
+ if (rowHeight > this.document.page.contentHeight) {
5283
+ console.warn(new Error(`Row ${rowIndex} requested more than the safe page height, row has been clamped`).stack.slice(7));
5284
+ this._rowHeights[rowIndex] = this.document.page.maxY() - this._rowYPos[rowIndex];
5285
+ } else if (this._rowYPos[rowIndex] + rowHeight >= this.document.page.maxY()) {
5286
+ this._rowYPos[rowIndex] = this.document.page.margins.top;
5287
+ newPage = true;
5288
+ }
5289
+ return {
5290
+ newPage,
5291
+ toRender: toRender.map(cell => measureCell.call(this, cell, rowHeight))
5292
+ };
5293
+ }
5294
+ function measureCell(cell, rowHeight) {
5295
+ let cellWidth = 0;
5296
+ for (let i = 0; i < cell.colSpan; i++) {
5297
+ cellWidth += this._columnWidths[cell.colIndex + i];
5298
+ }
5299
+ let cellHeight = rowHeight;
5300
+ if (cellHeight === 'auto') {
5301
+ cellHeight = this.document.page.contentHeight;
5302
+ } else {
5303
+ for (let i = 0; i < cell.rowSpan - 1; i++) {
5304
+ cellHeight += this._rowHeights[cell.rowIndex + i];
5305
+ }
5306
+ }
5307
+ const textAllocatedWidth = cellWidth - cell.padding.left - cell.padding.right;
5308
+ const textAllocatedHeight = cellHeight - cell.padding.top - cell.padding.bottom;
5309
+ const rotation = cell.textOptions.rotation ?? 0;
5310
+ const {
5311
+ width: textMaxWidth,
5312
+ height: textMaxHeight
5313
+ } = computeBounds(rotation, textAllocatedWidth, textAllocatedHeight);
5314
+ const textOptions = {
5315
+ align: cell.align.x,
5316
+ ellipsis: true,
5317
+ stroke: cell.textStroke > 0,
5318
+ fill: true,
5319
+ width: textMaxWidth,
5320
+ height: textMaxHeight,
5321
+ rotation,
5322
+ ...cell.textOptions
5323
+ };
5324
+ let textBounds = {
5325
+ x: 0,
5326
+ y: 0,
5327
+ width: 0,
5328
+ height: 0
5329
+ };
5330
+ if (cell.text) {
5331
+ const rollbackFont = this.document._fontSource;
5332
+ const rollbackFontSize = this.document._fontSize;
5333
+ const rollbackFontFamily = this.document._fontFamily;
5334
+ if (cell.font?.src) this.document.font(cell.font.src, cell.font?.family);
5335
+ if (cell.font?.size) this.document.fontSize(cell.font.size);
5336
+ const unRotatedTextBounds = this.document.boundsOfString(cell.text, 0, 0, {
5337
+ ...textOptions,
5338
+ rotation: 0
5339
+ });
5340
+ textOptions.width = unRotatedTextBounds.width;
5341
+ textOptions.height = unRotatedTextBounds.height;
5342
+ textBounds = this.document.boundsOfString(cell.text, 0, 0, textOptions);
5343
+ this.document.font(rollbackFont, rollbackFontFamily, rollbackFontSize);
5344
+ }
5345
+ return {
5346
+ ...cell,
5347
+ textOptions,
5348
+ x: this._columnXPos[cell.colIndex],
5349
+ y: this._rowYPos[cell.rowIndex],
5350
+ textX: this._columnXPos[cell.colIndex] + cell.padding.left,
5351
+ textY: this._rowYPos[cell.rowIndex] + cell.padding.top,
5352
+ width: cellWidth,
5353
+ height: cellHeight,
5354
+ textAllocatedHeight,
5355
+ textAllocatedWidth,
5356
+ textBounds
5357
+ };
5358
+ }
5359
+ function computeBounds(rotation, allocWidth, allocHeight) {
5360
+ let textMaxWidth, textMaxHeight;
5361
+ const cos = cosine(rotation);
5362
+ const sin = sine(rotation);
5363
+ if (rotation === 0 || rotation === 180) {
5364
+ textMaxWidth = allocWidth;
5365
+ textMaxHeight = allocHeight;
5366
+ } else if (rotation === 90 || rotation === 270) {
5367
+ textMaxWidth = allocHeight;
5368
+ textMaxHeight = allocWidth;
5369
+ } else if (rotation < 90 || rotation > 180 && rotation < 270) {
5370
+ textMaxWidth = allocWidth / (2 * cos);
5371
+ textMaxHeight = allocWidth / (2 * sin);
5372
+ } else {
5373
+ textMaxHeight = allocWidth / (2 * cos);
5374
+ textMaxWidth = allocWidth / (2 * sin);
5375
+ }
5376
+ const EF = sin * textMaxWidth;
5377
+ const FG = cos * textMaxHeight;
5378
+ if (EF + FG > allocHeight) {
5379
+ const denominator = cos * cos - sin * sin;
5380
+ if (rotation === 0 || rotation === 180) {
5381
+ textMaxWidth = allocWidth;
5382
+ textMaxHeight = allocHeight;
5383
+ } else if (rotation === 90 || rotation === 270) {
5384
+ textMaxWidth = allocHeight;
5385
+ textMaxHeight = allocWidth;
5386
+ } else if (rotation < 90 || rotation > 180 && rotation < 270) {
5387
+ textMaxWidth = (allocWidth * cos - allocHeight * sin) / denominator;
5388
+ textMaxHeight = (allocHeight * cos - allocWidth * sin) / denominator;
5389
+ } else {
5390
+ textMaxHeight = (allocWidth * cos - allocHeight * sin) / denominator;
5391
+ textMaxWidth = (allocHeight * cos - allocWidth * sin) / denominator;
5392
+ }
5393
+ }
5394
+ return {
5395
+ width: Math.abs(textMaxWidth),
5396
+ height: Math.abs(textMaxHeight)
5397
+ };
5398
+ }
5399
+
5400
+ function accommodateTable() {
5401
+ const structParent = this.opts.structParent;
5402
+ if (structParent) {
5403
+ this._tableStruct = this.document.struct('Table');
5404
+ this._tableStruct.dictionary.data.ID = this._id;
5405
+ if (structParent instanceof PDFStructureElement) {
5406
+ structParent.add(this._tableStruct);
5407
+ } else if (structParent instanceof PDFDocument) {
5408
+ structParent.addStructure(this._tableStruct);
5409
+ }
5410
+ this._headerRowLookup = {};
5411
+ this._headerColumnLookup = {};
5412
+ }
5413
+ }
5414
+ function accommodateCleanup() {
5415
+ if (this._tableStruct) this._tableStruct.end();
5416
+ }
5417
+ function accessibleRow(row, rowIndex, renderCell) {
5418
+ const rowStruct = this.document.struct('TR');
5419
+ rowStruct.dictionary.data.ID = new String(`${this._id}-${rowIndex}`);
5420
+ this._tableStruct.add(rowStruct);
5421
+ row.forEach(cell => renderCell(cell, rowStruct));
5422
+ rowStruct.end();
5423
+ }
5424
+ function accessibleCell(cell, rowStruct, callback) {
5425
+ const doc = this.document;
5426
+ const cellStruct = doc.struct(cell.type, {
5427
+ title: cell.title
5428
+ });
5429
+ cellStruct.dictionary.data.ID = cell.id;
5430
+ rowStruct.add(cellStruct);
5431
+ const padding = cell.padding;
5432
+ const border = cell.border;
5433
+ const attributes = {
5434
+ O: 'Table',
5435
+ Width: cell.width,
5436
+ Height: cell.height,
5437
+ Padding: [padding.top, padding.bottom, padding.left, padding.right],
5438
+ RowSpan: cell.rowSpan > 1 ? cell.rowSpan : undefined,
5439
+ ColSpan: cell.colSpan > 1 ? cell.colSpan : undefined,
5440
+ BorderThickness: [border.top, border.bottom, border.left, border.right]
5441
+ };
5442
+ if (cell.type === 'TH') {
5443
+ if (cell.scope === 'Row' || cell.scope === 'Both') {
5444
+ for (let i = 0; i < cell.rowSpan; i++) {
5445
+ if (!this._headerRowLookup[cell.rowIndex + i]) {
5446
+ this._headerRowLookup[cell.rowIndex + i] = [];
5447
+ }
5448
+ this._headerRowLookup[cell.rowIndex + i].push(cell.id);
5449
+ }
5450
+ attributes.Scope = cell.scope;
5451
+ }
5452
+ if (cell.scope === 'Column' || cell.scope === 'Both') {
5453
+ for (let i = 0; i < cell.colSpan; i++) {
5454
+ if (!this._headerColumnLookup[cell.colIndex + i]) {
5455
+ this._headerColumnLookup[cell.colIndex + i] = [];
5456
+ }
5457
+ this._headerColumnLookup[cell.colIndex + i].push(cell.id);
5458
+ }
5459
+ attributes.Scope = cell.scope;
5460
+ }
5461
+ }
5462
+ const Headers = new Set([...Array.from({
5463
+ length: cell.colSpan
5464
+ }, (_, i) => this._headerColumnLookup[cell.colIndex + i]).flat(), ...Array.from({
5465
+ length: cell.rowSpan
5466
+ }, (_, i) => this._headerRowLookup[cell.rowIndex + i]).flat()].filter(Boolean));
5467
+ if (Headers.size) attributes.Headers = Array.from(Headers);
5468
+ const normalizeColor = doc._normalizeColor;
5469
+ if (cell.backgroundColor != null) {
5470
+ attributes.BackgroundColor = normalizeColor(cell.backgroundColor);
5471
+ }
5472
+ const hasBorder = [border.top, border.bottom, border.left, border.right];
5473
+ if (hasBorder.some(x => x)) {
5474
+ const borderColor = cell.borderColor;
5475
+ 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];
5476
+ }
5477
+ Object.keys(attributes).forEach(key => attributes[key] === undefined && delete attributes[key]);
5478
+ cellStruct.dictionary.data.A = doc.ref(attributes);
5479
+ cellStruct.add(callback);
5480
+ cellStruct.end();
5481
+ cellStruct.dictionary.data.A.end();
5482
+ }
5483
+
5484
+ function renderRow(row, rowIndex) {
5485
+ if (this._tableStruct) {
5486
+ accessibleRow.call(this, row, rowIndex, renderCell.bind(this));
5487
+ } else {
5488
+ row.forEach(cell => renderCell.call(this, cell));
5489
+ }
5490
+ return this._rowYPos[rowIndex] + this._rowHeights[rowIndex];
5491
+ }
5492
+ function renderCell(cell, rowStruct) {
5493
+ const cellRenderer = () => {
5494
+ if (cell.backgroundColor != null) {
5495
+ this.document.save().rect(cell.x, cell.y, cell.width, cell.height).fill(cell.backgroundColor).restore();
5496
+ }
5497
+ renderBorder.call(this, cell.border, cell.borderColor, cell.x, cell.y, cell.width, cell.height);
5498
+ if (cell.debug) {
5499
+ this.document.save();
5500
+ this.document.dash(1, {
5501
+ space: 1
5502
+ }).lineWidth(1).strokeOpacity(0.3);
5503
+ this.document.rect(cell.x, cell.y, cell.width, cell.height).stroke('green');
5504
+ this.document.restore();
5505
+ }
5506
+ if (cell.text) renderCellText.call(this, cell);
5507
+ };
5508
+ if (rowStruct) accessibleCell.call(this, cell, rowStruct, cellRenderer);else cellRenderer();
5509
+ }
5510
+ function renderCellText(cell) {
5511
+ const doc = this.document;
5512
+ const rollbackFont = doc._fontSource;
5513
+ const rollbackFontSize = doc._fontSize;
5514
+ const rollbackFontFamily = doc._fontFamily;
5515
+ if (cell.customFont) {
5516
+ if (cell.font.src) doc.font(cell.font.src, cell.font.family);
5517
+ if (cell.font.size) doc.fontSize(cell.font.size);
5518
+ }
5519
+ const x = cell.textX;
5520
+ const y = cell.textY;
5521
+ const Ah = cell.textAllocatedHeight;
5522
+ const Aw = cell.textAllocatedWidth;
5523
+ const Cw = cell.textBounds.width;
5524
+ const Ch = cell.textBounds.height;
5525
+ const Ox = -cell.textBounds.x;
5526
+ const Oy = -cell.textBounds.y;
5527
+ const PxScale = cell.align.x === 'right' ? 1 : cell.align.x === 'center' ? 0.5 : 0;
5528
+ const Px = (Aw - Cw) * PxScale;
5529
+ const PyScale = cell.align.y === 'bottom' ? 1 : cell.align.y === 'center' ? 0.5 : 0;
5530
+ const Py = (Ah - Ch) * PyScale;
5531
+ const dx = Px + Ox;
5532
+ const dy = Py + Oy;
5533
+ if (cell.debug) {
5534
+ doc.save();
5535
+ doc.dash(1, {
5536
+ space: 1
5537
+ }).lineWidth(1).strokeOpacity(0.3);
5538
+ if (cell.text) {
5539
+ 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');
5540
+ }
5541
+ doc.rect(x, y, Aw, Ah).stroke('orange');
5542
+ doc.restore();
5543
+ }
5544
+ doc.save().rect(x, y, Aw, Ah).clip();
5545
+ doc.fillColor(cell.textColor).strokeColor(cell.textStrokeColor);
5546
+ if (cell.textStroke > 0) doc.lineWidth(cell.textStroke);
5547
+ doc.text(cell.text, x + dx, y + dy, cell.textOptions);
5548
+ doc.restore();
5549
+ if (cell.font) doc.font(rollbackFont, rollbackFontFamily, rollbackFontSize);
5550
+ }
5551
+ function renderBorder(border, borderColor, x, y, width, height, mask) {
5552
+ border = Object.fromEntries(Object.entries(border).map(([k, v]) => [k, mask && !mask[k] ? 0 : v]));
5553
+ const doc = this.document;
5554
+ if ([border.right, border.bottom, border.left].every(val => val === border.top)) {
5555
+ if (border.top > 0) {
5556
+ doc.save().lineWidth(border.top).rect(x, y, width, height).stroke(borderColor.top).restore();
5557
+ }
5558
+ } else {
5559
+ if (border.top > 0) {
5560
+ doc.save().lineWidth(border.top).moveTo(x, y).lineTo(x + width, y).stroke(borderColor.top).restore();
5561
+ }
5562
+ if (border.right > 0) {
5563
+ doc.save().lineWidth(border.right).moveTo(x + width, y).lineTo(x + width, y + height).stroke(borderColor.right).restore();
5564
+ }
5565
+ if (border.bottom > 0) {
5566
+ doc.save().lineWidth(border.bottom).moveTo(x + width, y + height).lineTo(x, y + height).stroke(borderColor.bottom).restore();
5567
+ }
5568
+ if (border.left > 0) {
5569
+ doc.save().lineWidth(border.left).moveTo(x, y + height).lineTo(x, y).stroke(borderColor.left).restore();
5570
+ }
5571
+ }
5572
+ }
5573
+
5574
+ class PDFTable {
5575
+ constructor(document, opts = {}) {
5576
+ this.document = document;
5577
+ this.opts = Object.freeze(opts);
5578
+ normalizeTable.call(this);
5579
+ accommodateTable.call(this);
5580
+ this._currRowIndex = 0;
5581
+ this._ended = false;
5582
+ if (opts.data) {
5583
+ for (const row of opts.data) this.row(row);
5584
+ return this.end();
5585
+ }
5586
+ }
5587
+ row(row, lastRow = false) {
5588
+ if (this._ended) {
5589
+ throw new Error(`Table was marked as ended on row ${this._currRowIndex}`);
5590
+ }
5591
+ row = Array.from(row);
5592
+ row = normalizeRow.call(this, row, this._currRowIndex);
5593
+ if (this._currRowIndex === 0) ensure.call(this, row);
5594
+ const {
5595
+ newPage,
5596
+ toRender
5597
+ } = measure.call(this, row, this._currRowIndex);
5598
+ if (newPage) this.document.continueOnNewPage();
5599
+ const yPos = renderRow.call(this, toRender, this._currRowIndex);
5600
+ this.document.x = this._position.x;
5601
+ this.document.y = yPos;
5602
+ if (lastRow) return this.end();
5603
+ this._currRowIndex++;
5604
+ return this;
5605
+ }
5606
+ end() {
5607
+ while (this._rowBuffer?.size) this.row([]);
5608
+ this._ended = true;
5609
+ accommodateCleanup.call(this);
5610
+ return this.document;
5611
+ }
5612
+ }
5613
+
5614
+ var TableMixin = {
5615
+ initTables() {
5616
+ this._tableIndex = 0;
5617
+ },
5618
+ table(opts) {
5619
+ return new PDFTable(this, opts);
5620
+ }
5621
+ };
5622
+
5383
5623
  class PDFMetadata {
5384
5624
  constructor() {
5385
5625
  this._metadata = `
@@ -5421,7 +5661,7 @@ var MetadataMixin = {
5421
5661
  _addInfo() {
5422
5662
  this.appendXML(`
5423
5663
  <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>
5664
+ <xmp:CreateDate>${this.info.CreationDate.toISOString().split('.')[0] + 'Z'}</xmp:CreateDate>
5425
5665
  <xmp:CreatorTool>${this.info.Creator}</xmp:CreatorTool>
5426
5666
  </rdf:Description>
5427
5667
  `);
@@ -5474,11 +5714,6 @@ var MetadataMixin = {
5474
5714
  endMetadata() {
5475
5715
  this._addInfo();
5476
5716
  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
5717
  if (this.version != 1.3) {
5483
5718
  this.metadataRef = this.ref({
5484
5719
  length: this.metadata.getLength(),
@@ -5493,16 +5728,10 @@ var MetadataMixin = {
5493
5728
  }
5494
5729
  };
5495
5730
 
5496
- /*
5497
- PDFDocument - represents an entire PDF document
5498
- By Devon Govett
5499
- */
5500
5731
  class PDFDocument extends stream.Readable {
5501
5732
  constructor(options = {}) {
5502
5733
  super(options);
5503
5734
  this.options = options;
5504
-
5505
- // PDF version
5506
5735
  switch (options.pdfVersion) {
5507
5736
  case '1.4':
5508
5737
  this.version = 1.4;
@@ -5521,13 +5750,9 @@ class PDFDocument extends stream.Readable {
5521
5750
  this.version = 1.3;
5522
5751
  break;
5523
5752
  }
5524
-
5525
- // Whether streams should be compressed
5526
5753
  this.compress = this.options.compress != null ? this.options.compress : true;
5527
5754
  this._pageBuffer = [];
5528
5755
  this._pageBufferStart = 0;
5529
-
5530
- // The PDF object store
5531
5756
  this._offsets = [];
5532
5757
  this._waiting = 0;
5533
5758
  this._ended = false;
@@ -5548,11 +5773,7 @@ class PDFDocument extends stream.Readable {
5548
5773
  if (this.options.lang) {
5549
5774
  this._root.data.Lang = new String(this.options.lang);
5550
5775
  }
5551
-
5552
- // The current page
5553
5776
  this.page = null;
5554
-
5555
- // Initialize mixins
5556
5777
  this.initMetadata();
5557
5778
  this.initColor();
5558
5779
  this.initVector();
@@ -5561,9 +5782,8 @@ class PDFDocument extends stream.Readable {
5561
5782
  this.initImages();
5562
5783
  this.initOutline();
5563
5784
  this.initMarkings(options);
5785
+ this.initTables();
5564
5786
  this.initSubset(options);
5565
-
5566
- // Initialize the metadata
5567
5787
  this.info = {
5568
5788
  Producer: 'PDFKit',
5569
5789
  Creator: 'PDFKit',
@@ -5580,21 +5800,10 @@ class PDFDocument extends stream.Readable {
5580
5800
  DisplayDocTitle: true
5581
5801
  });
5582
5802
  }
5583
-
5584
- // Generate file ID
5585
5803
  this._id = PDFSecurity.generateFileID(this.info);
5586
-
5587
- // Initialize security settings
5588
5804
  this._security = PDFSecurity.create(this, options);
5589
-
5590
- // Write the header
5591
- // PDF version
5592
5805
  this._write(`%PDF-${this.version}`);
5593
-
5594
- // 4 binary chars, as recommended by the spec
5595
5806
  this._write('%\xFF\xFF\xFF\xFF');
5596
-
5597
- // Add the first page
5598
5807
  if (this.options.autoFirstPage !== false) {
5599
5808
  this.addPage();
5600
5809
  }
@@ -5605,27 +5814,16 @@ class PDFDocument extends stream.Readable {
5605
5814
  options
5606
5815
  } = this);
5607
5816
  }
5608
-
5609
- // end the current page if needed
5610
5817
  if (!this.options.bufferPages) {
5611
5818
  this.flushPages();
5612
5819
  }
5613
-
5614
- // create a page object
5615
5820
  this.page = new PDFPage(this, options);
5616
5821
  this._pageBuffer.push(this.page);
5617
-
5618
- // add the page to the object store
5619
5822
  const pages = this._root.data.Pages.data;
5620
5823
  pages.Kids.push(this.page.dictionary);
5621
5824
  pages.Count++;
5622
-
5623
- // reset x and y coordinates
5624
5825
  this.x = this.page.margins.left;
5625
5826
  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
5827
  this._ctm = [1, 0, 0, 1, 0, 0];
5630
5828
  this.transform(1, 0, 0, -1, 0, this.page.height);
5631
5829
  this.emit('pageAdded');
@@ -5633,7 +5831,7 @@ class PDFDocument extends stream.Readable {
5633
5831
  }
5634
5832
  continueOnNewPage(options) {
5635
5833
  const pageMarkings = this.endPageMarkings(this.page);
5636
- this.addPage(options);
5834
+ this.addPage(options ?? this.page._options);
5637
5835
  this.initPageMarkings(pageMarkings);
5638
5836
  return this;
5639
5837
  }
@@ -5651,8 +5849,6 @@ class PDFDocument extends stream.Readable {
5651
5849
  return this.page = page;
5652
5850
  }
5653
5851
  flushPages() {
5654
- // this local variable exists so we're future-proof against
5655
- // reentrant calls to flushPages.
5656
5852
  const pages = this._pageBuffer;
5657
5853
  this._pageBuffer = [];
5658
5854
  this._pageBufferStart += pages.length;
@@ -5673,13 +5869,10 @@ class PDFDocument extends stream.Readable {
5673
5869
  }
5674
5870
  addNamedEmbeddedFile(name, ref) {
5675
5871
  if (!this._root.data.Names.data.EmbeddedFiles) {
5676
- // disabling /Limits for this tree fixes attachments not showing in Adobe Reader
5677
5872
  this._root.data.Names.data.EmbeddedFiles = new PDFNameTree({
5678
5873
  limits: false
5679
5874
  });
5680
5875
  }
5681
-
5682
- // add filespec to EmbeddedFiles
5683
5876
  this._root.data.Names.data.EmbeddedFiles.add(name, ref);
5684
5877
  }
5685
5878
  addNamedJavaScript(name, js) {
@@ -5694,19 +5887,17 @@ class PDFDocument extends stream.Readable {
5694
5887
  }
5695
5888
  ref(data) {
5696
5889
  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
5890
+ this._offsets.push(null);
5698
5891
  this._waiting++;
5699
5892
  return ref;
5700
5893
  }
5701
5894
  _read() {}
5702
- // do nothing, but this method is required by node
5703
-
5704
5895
  _write(data) {
5705
5896
  if (!Buffer.isBuffer(data)) {
5706
5897
  data = Buffer.from(data + '\n', 'binary');
5707
5898
  }
5708
5899
  this.push(data);
5709
- return this._offset += data.length;
5900
+ this._offset += data.length;
5710
5901
  }
5711
5902
  addContent(data) {
5712
5903
  this.page.write(data);
@@ -5716,7 +5907,7 @@ class PDFDocument extends stream.Readable {
5716
5907
  this._offsets[ref.id - 1] = ref.offset;
5717
5908
  if (--this._waiting === 0 && this._ended) {
5718
5909
  this._finalize();
5719
- return this._ended = false;
5910
+ this._ended = false;
5720
5911
  }
5721
5912
  }
5722
5913
  end() {
@@ -5753,13 +5944,12 @@ class PDFDocument extends stream.Readable {
5753
5944
  this._security.end();
5754
5945
  }
5755
5946
  if (this._waiting === 0) {
5756
- return this._finalize();
5947
+ this._finalize();
5757
5948
  } else {
5758
- return this._ended = true;
5949
+ this._ended = true;
5759
5950
  }
5760
5951
  }
5761
5952
  _finalize() {
5762
- // generate xref
5763
5953
  const xRefOffset = this._offset;
5764
5954
  this._write('xref');
5765
5955
  this._write(`0 ${this._offsets.length + 1}`);
@@ -5768,8 +5958,6 @@ class PDFDocument extends stream.Readable {
5768
5958
  offset = `0000000000${offset}`.slice(-10);
5769
5959
  this._write(offset + ' 00000 n ');
5770
5960
  }
5771
-
5772
- // trailer
5773
5961
  const trailer = {
5774
5962
  Size: this._offsets.length + 1,
5775
5963
  Root: this._root,
@@ -5784,9 +5972,7 @@ class PDFDocument extends stream.Readable {
5784
5972
  this._write('startxref');
5785
5973
  this._write(`${xRefOffset}`);
5786
5974
  this._write('%%EOF');
5787
-
5788
- // end the stream
5789
- return this.push(null);
5975
+ this.push(null);
5790
5976
  }
5791
5977
  toString() {
5792
5978
  return '[object PDFDocument]';
@@ -5807,6 +5993,7 @@ mixin(MarkingsMixin);
5807
5993
  mixin(AcroFormMixin);
5808
5994
  mixin(AttachmentsMixin);
5809
5995
  mixin(SubsetMixin);
5996
+ mixin(TableMixin);
5810
5997
  PDFDocument.LineWrapper = LineWrapper;
5811
5998
 
5812
5999
  module.exports = PDFDocument;