pdfkit 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/js/pdfkit.es.js CHANGED
@@ -8,24 +8,16 @@ import LineBreaker from 'linebreak';
8
8
  import exif from 'jpeg-exif';
9
9
  import PNG from 'png-js';
10
10
 
11
- /*
12
- PDFAbstractReference - abstract class for PDF reference
13
- */
14
-
15
11
  class PDFAbstractReference {
16
12
  toString() {
17
13
  throw new Error('Must be implemented by subclasses');
18
14
  }
19
15
  }
20
16
 
21
- /*
22
- PDFTree - abstract base class for name and number tree objects
23
- */
24
17
  class PDFTree {
25
18
  constructor() {
26
19
  let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
27
20
  this._items = {};
28
- // disable /Limits output for this tree
29
21
  this.limits = typeof options.limits === 'boolean' ? options.limits : true;
30
22
  }
31
23
  add(key, val) {
@@ -35,7 +27,6 @@ class PDFTree {
35
27
  return this._items[key];
36
28
  }
37
29
  toString() {
38
- // Needs to be sorted by key
39
30
  const sortedKeys = Object.keys(this._items).sort((a, b) => this._compareKeys(a, b));
40
31
  const out = ['<<'];
41
32
  if (this.limits && sortedKeys.length > 1) {
@@ -51,15 +42,13 @@ class PDFTree {
51
42
  out.push('>>');
52
43
  return out.join('\n');
53
44
  }
54
- _compareKeys(/*a, b*/
55
- ) {
45
+ _compareKeys() {
56
46
  throw new Error('Must be implemented by subclasses');
57
47
  }
58
48
  _keysName() {
59
49
  throw new Error('Must be implemented by subclasses');
60
50
  }
61
- _dataForKey(/*k*/
62
- ) {
51
+ _dataForKey() {
63
52
  throw new Error('Must be implemented by subclasses');
64
53
  }
65
54
  }
@@ -84,10 +73,6 @@ class SpotColor {
84
73
  }
85
74
  }
86
75
 
87
- /*
88
- PDFObject - converts JavaScript types into their corresponding PDF types.
89
- By Devon Govett
90
- */
91
76
  const pad = (str, length) => (Array(length + 1).join('0') + str).slice(-length);
92
77
  const escapableRe = /[\n\r\t\b\f()\\]/g;
93
78
  const escapable = {
@@ -100,8 +85,6 @@ const escapable = {
100
85
  '(': '\\(',
101
86
  ')': '\\)'
102
87
  };
103
-
104
- // Convert little endian UTF-16 to big endian
105
88
  const swapBytes = function (buff) {
106
89
  const l = buff.length;
107
90
  if (l & 0x01) {
@@ -118,14 +101,10 @@ const swapBytes = function (buff) {
118
101
  class PDFObject {
119
102
  static convert(object) {
120
103
  let encryptFn = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 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) {
203
166
  let data = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
@@ -221,14 +184,14 @@ class PDFReference extends PDFAbstractReference {
221
184
  this.buffer.push(chunk);
222
185
  this.data.Length += chunk.length;
223
186
  if (this.compress) {
224
- return this.data.Filter = 'FlateDecode';
187
+ this.data.Filter = 'FlateDecode';
225
188
  }
226
189
  }
227
190
  end(chunk) {
228
191
  if (chunk) {
229
192
  this.write(chunk);
230
193
  }
231
- return this.finalize();
194
+ this.finalize();
232
195
  }
233
196
  finalize() {
234
197
  this.offset = this.document._offset;
@@ -248,7 +211,7 @@ class PDFReference extends PDFAbstractReference {
248
211
  if (this.buffer.length) {
249
212
  this.document._write('stream');
250
213
  this.document._write(this.buffer);
251
- this.buffer = []; // free up memory
214
+ this.buffer = [];
252
215
  this.document._write('\nendstream');
253
216
  }
254
217
  this.document._write('endobj');
@@ -259,10 +222,71 @@ class PDFReference extends PDFAbstractReference {
259
222
  }
260
223
  }
261
224
 
262
- /*
263
- PDFPage - represents a single page in the PDF document
264
- By Devon Govett
265
- */
225
+ function PDFNumber(n) {
226
+ return Math.fround(n);
227
+ }
228
+ function normalizeSides(sides) {
229
+ let defaultDefinition = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined;
230
+ let transformer = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : v => v;
231
+ if (sides == null || typeof sides === 'object' && Object.keys(sides).length === 0) {
232
+ sides = defaultDefinition;
233
+ }
234
+ if (sides == null || typeof sides !== 'object') {
235
+ sides = {
236
+ top: sides,
237
+ right: sides,
238
+ bottom: sides,
239
+ left: sides
240
+ };
241
+ } else if (Array.isArray(sides)) {
242
+ if (sides.length === 2) {
243
+ sides = {
244
+ vertical: sides[0],
245
+ horizontal: sides[1]
246
+ };
247
+ } else {
248
+ sides = {
249
+ top: sides[0],
250
+ right: sides[1],
251
+ bottom: sides[2],
252
+ left: sides[3]
253
+ };
254
+ }
255
+ }
256
+ if ('vertical' in sides || 'horizontal' in sides) {
257
+ sides = {
258
+ top: sides.vertical,
259
+ right: sides.horizontal,
260
+ bottom: sides.vertical,
261
+ left: sides.horizontal
262
+ };
263
+ }
264
+ return {
265
+ top: transformer(sides.top),
266
+ right: transformer(sides.right),
267
+ bottom: transformer(sides.bottom),
268
+ left: transformer(sides.left)
269
+ };
270
+ }
271
+ const MM_TO_CM = 1 / 10;
272
+ const CM_TO_IN = 1 / 2.54;
273
+ const PX_TO_IN = 1 / 96;
274
+ const IN_TO_PT = 72;
275
+ const PC_TO_PT = 12;
276
+ function cosine(a) {
277
+ if (a === 0) return 1;
278
+ if (a === 90) return 0;
279
+ if (a === 180) return -1;
280
+ if (a === 270) return 0;
281
+ return Math.cos(a * Math.PI / 180);
282
+ }
283
+ function sine(a) {
284
+ if (a === 0) return 0;
285
+ if (a === 90) return 1;
286
+ if (a === 180) return 0;
287
+ if (a === 270) return -1;
288
+ return Math.sin(a * Math.PI / 180);
289
+ }
266
290
 
267
291
  const DEFAULT_MARGINS = {
268
292
  top: 72,
@@ -326,35 +350,19 @@ class PDFPage {
326
350
  constructor(document) {
327
351
  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
328
352
  this.document = document;
353
+ this._options = options;
329
354
  this.size = options.size || 'letter';
330
355
  this.layout = options.layout || 'portrait';
331
-
332
- // process margins
333
- if (typeof options.margin === 'number') {
334
- this.margins = {
335
- top: options.margin,
336
- left: options.margin,
337
- bottom: options.margin,
338
- right: options.margin
339
- };
340
-
341
- // default to 1 inch margins
342
- } else {
343
- this.margins = options.margins || DEFAULT_MARGINS;
344
- }
345
-
346
- // calculate page dimensions
347
356
  const dimensions = Array.isArray(this.size) ? this.size : SIZES[this.size.toUpperCase()];
348
357
  this.width = dimensions[this.layout === 'portrait' ? 0 : 1];
349
358
  this.height = dimensions[this.layout === 'portrait' ? 1 : 0];
350
359
  this.content = this.document.ref();
351
-
352
- // Initialize the Font, XObject, and ExtGState dictionaries
360
+ if (options.font) document.font(options.font, options.fontFamily);
361
+ if (options.fontSize) document.fontSize(options.fontSize);
362
+ this.margins = normalizeSides(options.margin ?? options.margins, DEFAULT_MARGINS, x => document.sizeToPoint(x, 0, this));
353
363
  this.resources = this.document.ref({
354
364
  ProcSet: ['PDF', 'Text', 'ImageB', 'ImageC', 'ImageI']
355
365
  });
356
-
357
- // The page dictionary
358
366
  this.dictionary = this.document.ref({
359
367
  Type: 'Page',
360
368
  Parent: this.document._root.data.Pages,
@@ -364,8 +372,6 @@ class PDFPage {
364
372
  });
365
373
  this.markings = [];
366
374
  }
367
-
368
- // Lazily create these objects
369
375
  get fonts() {
370
376
  const data = this.resources.data;
371
377
  return data.Font != null ? data.Font : data.Font = {};
@@ -394,14 +400,18 @@ class PDFPage {
394
400
  const data = this.dictionary.data;
395
401
  return data.StructParents != null ? data.StructParents : data.StructParents = this.document.createStructParentTreeNextKey();
396
402
  }
403
+ get contentWidth() {
404
+ return this.width - this.margins.left - this.margins.right;
405
+ }
406
+ get contentHeight() {
407
+ return this.height - this.margins.top - this.margins.bottom;
408
+ }
397
409
  maxY() {
398
410
  return this.height - this.margins.bottom;
399
411
  }
400
412
  write(chunk) {
401
413
  return this.content.write(chunk);
402
414
  }
403
-
404
- // Set tab order if document is tagged for accessibility.
405
415
  _setTabOrder() {
406
416
  if (!this.dictionary.Tabs && this.document.hasMarkInfoDictionary()) {
407
417
  this.dictionary.data.Tabs = 'S';
@@ -419,191 +429,57 @@ class PDFPage {
419
429
  }
420
430
  }
421
431
 
422
- /*
423
- PDFNameTree - represents a name tree object
424
- */
425
432
  class PDFNameTree extends PDFTree {
426
433
  _compareKeys(a, b) {
427
434
  return a.localeCompare(b);
428
435
  }
429
436
  _keysName() {
430
- return "Names";
437
+ return 'Names';
431
438
  }
432
439
  _dataForKey(k) {
433
440
  return new String(k);
434
441
  }
435
442
  }
436
443
 
437
- /**
438
- * Check if value is in a range group.
439
- * @param {number} value
440
- * @param {number[]} rangeGroup
441
- * @returns {boolean}
442
- */
443
444
  function inRange(value, rangeGroup) {
444
445
  if (value < rangeGroup[0]) return false;
445
446
  let startRange = 0;
446
447
  let endRange = rangeGroup.length / 2;
447
448
  while (startRange <= endRange) {
448
449
  const middleRange = Math.floor((startRange + endRange) / 2);
449
-
450
- // actual array index
451
450
  const arrayIndex = middleRange * 2;
452
-
453
- // Check if value is in range pointed by actual index
454
451
  if (value >= rangeGroup[arrayIndex] && value <= rangeGroup[arrayIndex + 1]) {
455
452
  return true;
456
453
  }
457
454
  if (value > rangeGroup[arrayIndex + 1]) {
458
- // Search Right Side Of Array
459
455
  startRange = middleRange + 1;
460
456
  } else {
461
- // Search Left Side Of Array
462
457
  endRange = middleRange - 1;
463
458
  }
464
459
  }
465
460
  return false;
466
461
  }
467
462
 
468
- // prettier-ignore-start
469
- /**
470
- * A.1 Unassigned code points in Unicode 3.2
471
- * @link https://tools.ietf.org/html/rfc3454#appendix-A.1
472
- */
473
463
  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];
474
- // prettier-ignore-end
475
-
476
464
  const isUnassignedCodePoint = character => inRange(character, unassigned_code_points);
477
-
478
- // prettier-ignore-start
479
- /**
480
- * B.1 Commonly mapped to nothing
481
- * @link https://tools.ietf.org/html/rfc3454#appendix-B.1
482
- */
483
465
  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];
484
- // prettier-ignore-end
485
-
486
466
  const isCommonlyMappedToNothing = character => inRange(character, commonly_mapped_to_nothing);
487
-
488
- // prettier-ignore-start
489
- /**
490
- * C.1.2 Non-ASCII space characters
491
- * @link https://tools.ietf.org/html/rfc3454#appendix-C.1.2
492
- */
493
- 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 */];
494
- // prettier-ignore-end
495
-
467
+ 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];
496
468
  const isNonASCIISpaceCharacter = character => inRange(character, non_ASCII_space_characters);
497
-
498
- // prettier-ignore-start
499
- const non_ASCII_controls_characters = [
500
- /**
501
- * C.2.2 Non-ASCII control characters
502
- * @link https://tools.ietf.org/html/rfc3454#appendix-C.2.2
503
- */
504
- 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] */];
505
- const non_character_codepoints = [
506
- /**
507
- * C.4 Non-character code points
508
- * @link https://tools.ietf.org/html/rfc3454#appendix-C.4
509
- */
510
- 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] */];
511
-
512
- /**
513
- * 2.3. Prohibited Output
514
- */
515
- const prohibited_characters = [
516
- /**
517
- * C.2.1 ASCII control characters
518
- * @link https://tools.ietf.org/html/rfc3454#appendix-C.2.1
519
- */
520
- 0, 0x001f /* [CONTROL CHARACTERS] */, 0x007f, 0x007f /* DELETE */,
521
- /**
522
- * C.8 Change display properties or are deprecated
523
- * @link https://tools.ietf.org/html/rfc3454#appendix-C.8
524
- */
525
- 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 */,
526
- /**
527
- * C.7 Inappropriate for canonical representation
528
- * @link https://tools.ietf.org/html/rfc3454#appendix-C.7
529
- */
530
- 0x2ff0, 0x2ffb /* [IDEOGRAPHIC DESCRIPTION CHARACTERS] */,
531
- /**
532
- * C.5 Surrogate codes
533
- * @link https://tools.ietf.org/html/rfc3454#appendix-C.5
534
- */
535
- 0xd800, 0xdfff,
536
- /**
537
- * C.3 Private use
538
- * @link https://tools.ietf.org/html/rfc3454#appendix-C.3
539
- */
540
- 0xe000, 0xf8ff /* [PRIVATE USE, PLANE 0] */,
541
- /**
542
- * C.6 Inappropriate for plain text
543
- * @link https://tools.ietf.org/html/rfc3454#appendix-C.6
544
- */
545
- 0xfff9, 0xfff9 /* INTERLINEAR ANNOTATION ANCHOR */, 0xfffa, 0xfffa /* INTERLINEAR ANNOTATION SEPARATOR */, 0xfffb, 0xfffb /* INTERLINEAR ANNOTATION TERMINATOR */, 0xfffc, 0xfffc /* OBJECT REPLACEMENT CHARACTER */, 0xfffd, 0xfffd /* REPLACEMENT CHARACTER */,
546
- /**
547
- * C.9 Tagging characters
548
- * @link https://tools.ietf.org/html/rfc3454#appendix-C.9
549
- */
550
- 0xe0001, 0xe0001 /* LANGUAGE TAG */, 0xe0020, 0xe007f /* [TAGGING CHARACTERS] */,
551
- /**
552
- * C.3 Private use
553
- * @link https://tools.ietf.org/html/rfc3454#appendix-C.3
554
- */
555
-
556
- 0xf0000, 0xffffd /* [PRIVATE USE, PLANE 15] */, 0x100000, 0x10fffd /* [PRIVATE USE, PLANE 16] */];
557
- // prettier-ignore-end
558
-
469
+ 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];
470
+ 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];
471
+ 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];
559
472
  const isProhibitedCharacter = character => inRange(character, non_ASCII_space_characters) || inRange(character, prohibited_characters) || inRange(character, non_ASCII_controls_characters) || inRange(character, non_character_codepoints);
560
-
561
- // prettier-ignore-start
562
- /**
563
- * D.1 Characters with bidirectional property "R" or "AL"
564
- * @link https://tools.ietf.org/html/rfc3454#appendix-D.1
565
- */
566
473
  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];
567
- // prettier-ignore-end
568
-
569
474
  const isBidirectionalRAL = character => inRange(character, bidirectional_r_al);
570
-
571
- // prettier-ignore-start
572
- /**
573
- * D.2 Characters with bidirectional property "L"
574
- * @link https://tools.ietf.org/html/rfc3454#appendix-D.2
575
- */
576
475
  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];
577
- // prettier-ignore-end
578
-
579
476
  const isBidirectionalL = character => inRange(character, bidirectional_l);
580
477
 
581
- // 2.1. Mapping
582
-
583
- /**
584
- * non-ASCII space characters [StringPrep, C.1.2] that can be
585
- * mapped to SPACE (U+0020)
586
- */
587
478
  const mapping2space = isNonASCIISpaceCharacter;
588
-
589
- /**
590
- * the "commonly mapped to nothing" characters [StringPrep, B.1]
591
- * that can be mapped to nothing.
592
- */
593
479
  const mapping2nothing = isCommonlyMappedToNothing;
594
-
595
- // utils
596
480
  const getCodePoint = character => character.codePointAt(0);
597
481
  const first = x => x[0];
598
482
  const last = x => x[x.length - 1];
599
-
600
- /**
601
- * Convert provided string into an array of Unicode Code Points.
602
- * Based on https://stackoverflow.com/a/21409165/1556249
603
- * and https://www.npmjs.com/package/code-point-at.
604
- * @param {string} input
605
- * @returns {number[]}
606
- */
607
483
  function toCodePoints(input) {
608
484
  const codepoints = [];
609
485
  const size = input.length;
@@ -621,14 +497,6 @@ function toCodePoints(input) {
621
497
  }
622
498
  return codepoints;
623
499
  }
624
-
625
- /**
626
- * SASLprep.
627
- * @param {string} input
628
- * @param {Object} opts
629
- * @param {boolean} opts.allowUnassigned
630
- * @returns {string}
631
- */
632
500
  function saslprep(input) {
633
501
  let opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
634
502
  if (typeof input !== 'string') {
@@ -637,49 +505,24 @@ function saslprep(input) {
637
505
  if (input.length === 0) {
638
506
  return '';
639
507
  }
640
-
641
- // 1. Map
642
- const mapped_input = toCodePoints(input)
643
- // 1.1 mapping to space
644
- .map(character => mapping2space(character) ? 0x20 : character)
645
- // 1.2 mapping to nothing
646
- .filter(character => !mapping2nothing(character));
647
-
648
- // 2. Normalize
508
+ const mapped_input = toCodePoints(input).map(character => mapping2space(character) ? 0x20 : character).filter(character => !mapping2nothing(character));
649
509
  const normalized_input = String.fromCodePoint.apply(null, mapped_input).normalize('NFKC');
650
510
  const normalized_map = toCodePoints(normalized_input);
651
-
652
- // 3. Prohibit
653
511
  const hasProhibited = normalized_map.some(isProhibitedCharacter);
654
512
  if (hasProhibited) {
655
513
  throw new Error('Prohibited character, see https://tools.ietf.org/html/rfc4013#section-2.3');
656
514
  }
657
-
658
- // Unassigned Code Points
659
515
  if (opts.allowUnassigned !== true) {
660
516
  const hasUnassigned = normalized_map.some(isUnassignedCodePoint);
661
517
  if (hasUnassigned) {
662
518
  throw new Error('Unassigned code point, see https://tools.ietf.org/html/rfc4013#section-2.5');
663
519
  }
664
520
  }
665
-
666
- // 4. check bidi
667
-
668
521
  const hasBidiRAL = normalized_map.some(isBidirectionalRAL);
669
522
  const hasBidiL = normalized_map.some(isBidirectionalL);
670
-
671
- // 4.1 If a string contains any RandALCat character, the string MUST NOT
672
- // contain any LCat character.
673
523
  if (hasBidiRAL && hasBidiL) {
674
524
  throw new Error('String must not contain RandALCat and LCat at the same time,' + ' see https://tools.ietf.org/html/rfc3454#section-6');
675
525
  }
676
-
677
- /**
678
- * 4.2 If a string contains any RandALCat character, a RandALCat
679
- * character MUST be the first character of the string, and a
680
- * RandALCat character MUST be the last character of the string.
681
- */
682
-
683
526
  const isFirstBidiRAL = isBidirectionalRAL(getCodePoint(first(normalized_input)));
684
527
  const isLastBidiRAL = isBidirectionalRAL(getCodePoint(last(normalized_input)));
685
528
  if (hasBidiRAL && !(isFirstBidiRAL && isLastBidiRAL)) {
@@ -688,16 +531,11 @@ function saslprep(input) {
688
531
  return normalized_input;
689
532
  }
690
533
 
691
- /*
692
- PDFSecurity - represents PDF security settings
693
- By Yang Liu <hi@zesik.com>
694
- */
695
534
  class PDFSecurity {
696
535
  static generateFileID() {
697
536
  let info = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
698
537
  let infoStr = `${info.CreationDate.getTime()}\n`;
699
538
  for (let key in info) {
700
- // eslint-disable-next-line no-prototype-builtins
701
539
  if (!info.hasOwnProperty(key)) {
702
540
  continue;
703
541
  }
@@ -1077,8 +915,6 @@ class PDFGradient$1 {
1077
915
  }
1078
916
  this.embedded = true;
1079
917
  this.matrix = m;
1080
-
1081
- // if the last stop comes before 100%, add a copy at 100%
1082
918
  const last = this.stops[stopsLength - 1];
1083
919
  if (last[0] < 1) {
1084
920
  this.stops.push([1, last[1], last[2]]);
@@ -1101,14 +937,11 @@ class PDFGradient$1 {
1101
937
  stops.push(fn);
1102
938
  fn.end();
1103
939
  }
1104
-
1105
- // if there are only two stops, we don't need a stitching function
1106
940
  if (stopsLength === 1) {
1107
941
  fn = stops[0];
1108
942
  } else {
1109
943
  fn = this.doc.ref({
1110
944
  FunctionType: 3,
1111
- // stitching function
1112
945
  Domain: [0, 1],
1113
946
  Functions: stops,
1114
947
  Bounds: bounds,
@@ -1189,7 +1022,6 @@ class PDFGradient$1 {
1189
1022
  return pattern;
1190
1023
  }
1191
1024
  apply(stroke) {
1192
- // apply gradient transform to existing document ctm
1193
1025
  const [m0, m1, m2, m3, m4, m5] = this.doc._ctm;
1194
1026
  const [m11, m12, m21, m22, dx, dy] = this.transform;
1195
1027
  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];
@@ -1252,10 +1084,6 @@ var Gradient = {
1252
1084
  PDFRadialGradient: PDFRadialGradient$1
1253
1085
  };
1254
1086
 
1255
- /*
1256
- PDF tiling pattern support. Uncolored only.
1257
- */
1258
-
1259
1087
  const underlyingColorSpaces = ['DeviceCMYK', 'DeviceRGB'];
1260
1088
  class PDFTilingPattern$1 {
1261
1089
  constructor(doc, bBox, xStep, yStep, stream) {
@@ -1266,23 +1094,16 @@ class PDFTilingPattern$1 {
1266
1094
  this.stream = stream;
1267
1095
  }
1268
1096
  createPattern() {
1269
- // no resources needed for our current usage
1270
- // required entry
1271
1097
  const resources = this.doc.ref();
1272
1098
  resources.end();
1273
- // apply default transform matrix (flipped in the default doc._ctm)
1274
- // see document.js & gradient.js
1275
1099
  const [m0, m1, m2, m3, m4, m5] = this.doc._ctm;
1276
1100
  const [m11, m12, m21, m22, dx, dy] = [1, 0, 0, 1, 0, 0];
1277
1101
  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];
1278
1102
  const pattern = this.doc.ref({
1279
1103
  Type: 'Pattern',
1280
1104
  PatternType: 1,
1281
- // tiling
1282
1105
  PaintType: 2,
1283
- // 1-colored, 2-uncolored
1284
1106
  TilingType: 2,
1285
- // 2-no distortion
1286
1107
  BBox: this.bBox,
1287
1108
  XStep: this.xStep,
1288
1109
  YStep: this.yStep,
@@ -1293,8 +1114,6 @@ class PDFTilingPattern$1 {
1293
1114
  return pattern;
1294
1115
  }
1295
1116
  embedPatternColorSpaces() {
1296
- // map each pattern to an underlying color space
1297
- // and embed on each page
1298
1117
  underlyingColorSpaces.forEach(csName => {
1299
1118
  const csId = this.getPatternColorSpaceId(csName);
1300
1119
  if (this.doc.page.colorSpaces[csId]) return;
@@ -1312,24 +1131,17 @@ class PDFTilingPattern$1 {
1312
1131
  this.id = 'P' + this.doc._patternCount;
1313
1132
  this.pattern = this.createPattern();
1314
1133
  }
1315
-
1316
- // patterns are embedded in each page
1317
1134
  if (!this.doc.page.patterns[this.id]) {
1318
1135
  this.doc.page.patterns[this.id] = this.pattern;
1319
1136
  }
1320
1137
  }
1321
1138
  apply(stroke, patternColor) {
1322
- // do any embedding/creating that might be needed
1323
1139
  this.embedPatternColorSpaces();
1324
1140
  this.embed();
1325
1141
  const normalizedColor = this.doc._normalizeColor(patternColor);
1326
1142
  if (!normalizedColor) throw Error(`invalid pattern color. (value: ${patternColor})`);
1327
-
1328
- // select one of the pattern color spaces
1329
1143
  const csId = this.getPatternColorSpaceId(this.doc._getColorSpace(normalizedColor));
1330
1144
  this.doc._setColorSpace(csId, stroke);
1331
-
1332
- // stroke/fill using the pattern and color (in the above underlying color space)
1333
1145
  const op = stroke ? 'SCN' : 'scn';
1334
1146
  return this.doc.addContent(`${normalizedColor.join(' ')} /${this.id} ${op}`);
1335
1147
  }
@@ -1349,11 +1161,10 @@ const {
1349
1161
  var ColorMixin = {
1350
1162
  initColor() {
1351
1163
  this.spotColors = {};
1352
- // The opacity dictionaries
1353
1164
  this._opacityRegistry = {};
1354
1165
  this._opacityCount = 0;
1355
1166
  this._patternCount = 0;
1356
- return this._gradCount = 0;
1167
+ this._gradCount = 0;
1357
1168
  },
1358
1169
  _normalizeColor(color) {
1359
1170
  if (typeof color === 'string') {
@@ -1370,10 +1181,8 @@ var ColorMixin = {
1370
1181
  }
1371
1182
  }
1372
1183
  if (Array.isArray(color)) {
1373
- // RGB
1374
1184
  if (color.length === 3) {
1375
1185
  color = color.map(part => part / 255);
1376
- // CMYK
1377
1186
  } else if (color.length === 4) {
1378
1187
  color = color.map(part => part / 100);
1379
1188
  }
@@ -1385,12 +1194,10 @@ var ColorMixin = {
1385
1194
  if (color instanceof PDFGradient) {
1386
1195
  color.apply(stroke);
1387
1196
  return true;
1388
- // see if tiling pattern, decode & apply it it
1389
1197
  } else if (Array.isArray(color) && color[0] instanceof PDFTilingPattern) {
1390
1198
  color[0].apply(stroke, color[1]);
1391
1199
  return true;
1392
1200
  }
1393
- // any other case should be a normal color and not a pattern
1394
1201
  return this._setColorCore(color, stroke);
1395
1202
  },
1396
1203
  _setColorCore(color, stroke) {
@@ -1424,9 +1231,6 @@ var ColorMixin = {
1424
1231
  if (set) {
1425
1232
  this.fillOpacity(opacity);
1426
1233
  }
1427
-
1428
- // save this for text wrapper, which needs to reset
1429
- // the fill color on new pages
1430
1234
  this._fillColor = [color, opacity];
1431
1235
  return this;
1432
1236
  },
@@ -1682,7 +1486,6 @@ const parse = function (path) {
1682
1486
  if (parameters[c] != null) {
1683
1487
  params = parameters[c];
1684
1488
  if (cmd) {
1685
- // save existing command
1686
1489
  if (curArg.length > 0) {
1687
1490
  args[args.length] = +curArg;
1688
1491
  }
@@ -1700,14 +1503,11 @@ const parse = function (path) {
1700
1503
  continue;
1701
1504
  }
1702
1505
  if (args.length === params) {
1703
- // handle reused commands
1704
1506
  ret[ret.length] = {
1705
1507
  cmd,
1706
1508
  args
1707
1509
  };
1708
1510
  args = [+curArg];
1709
-
1710
- // handle assumed commands
1711
1511
  if (cmd === 'M') {
1712
1512
  cmd = 'L';
1713
1513
  }
@@ -1718,8 +1518,6 @@ const parse = function (path) {
1718
1518
  args[args.length] = +curArg;
1719
1519
  }
1720
1520
  foundDecimal = c === '.';
1721
-
1722
- // fix for negative numbers or repeated decimals with no delimeter between commands
1723
1521
  curArg = ['-', '.'].includes(c) ? c : '';
1724
1522
  } else {
1725
1523
  curArg += c;
@@ -1728,18 +1526,13 @@ const parse = function (path) {
1728
1526
  }
1729
1527
  }
1730
1528
  }
1731
-
1732
- // add the last command
1733
1529
  if (curArg.length > 0) {
1734
1530
  if (args.length === params) {
1735
- // handle reused commands
1736
1531
  ret[ret.length] = {
1737
1532
  cmd,
1738
1533
  args
1739
1534
  };
1740
1535
  args = [+curArg];
1741
-
1742
- // handle assumed commands
1743
1536
  if (cmd === 'M') {
1744
1537
  cmd = 'L';
1745
1538
  }
@@ -1757,10 +1550,7 @@ const parse = function (path) {
1757
1550
  return ret;
1758
1551
  };
1759
1552
  const apply = function (commands, doc) {
1760
- // current point, control point, and subpath starting point
1761
1553
  cx = cy = px = py = sx = sy = 0;
1762
-
1763
- // run the commands
1764
1554
  for (let i = 0; i < commands.length; i++) {
1765
1555
  const c = commands[i];
1766
1556
  if (typeof runners[c.cmd] === 'function') {
@@ -1924,8 +1714,6 @@ const solveArc = function (doc, x, y, coords) {
1924
1714
  doc.bezierCurveTo(...bez);
1925
1715
  }
1926
1716
  };
1927
-
1928
- // from Inkscape svgtopdf, thanks!
1929
1717
  const arcToSegments = function (x, y, rx, ry, large, sweep, rotateX, ox, oy) {
1930
1718
  const th = rotateX * (Math.PI / 180);
1931
1719
  const sin_th = Math.sin(th);
@@ -2001,18 +1789,14 @@ class SVGPath {
2001
1789
  const {
2002
1790
  number: number$1
2003
1791
  } = PDFObject;
2004
-
2005
- // This constant is used to approximate a symmetrical arc using a cubic
2006
- // Bezier curve.
2007
1792
  const KAPPA = 4.0 * ((Math.sqrt(2) - 1.0) / 3.0);
2008
1793
  var VectorMixin = {
2009
1794
  initVector() {
2010
- this._ctm = [1, 0, 0, 1, 0, 0]; // current transformation matrix
2011
- return this._ctmStack = [];
1795
+ this._ctm = [1, 0, 0, 1, 0, 0];
1796
+ this._ctmStack = [];
2012
1797
  },
2013
1798
  save() {
2014
1799
  this._ctmStack.push(this._ctm.slice());
2015
- // TODO: save/restore colorspace and styles so not setting it unnessesarily all the time?
2016
1800
  return this.addContent('q');
2017
1801
  },
2018
1802
  restore() {
@@ -2086,8 +1870,6 @@ var VectorMixin = {
2086
1870
  r = 0;
2087
1871
  }
2088
1872
  r = Math.min(r, 0.5 * w, 0.5 * h);
2089
-
2090
- // amount to inset control points from corners (see `ellipse`)
2091
1873
  const c = r * (1.0 - KAPPA);
2092
1874
  this.moveTo(x + r, y);
2093
1875
  this.lineTo(x + w - r, y);
@@ -2101,7 +1883,6 @@ var VectorMixin = {
2101
1883
  return this.closePath();
2102
1884
  },
2103
1885
  ellipse(x, y, r1, r2) {
2104
- // based on http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas/2173084#2173084
2105
1886
  if (r2 == null) {
2106
1887
  r2 = r1;
2107
1888
  }
@@ -2131,10 +1912,8 @@ var VectorMixin = {
2131
1912
  const HALF_PI = 0.5 * Math.PI;
2132
1913
  let deltaAng = endAngle - startAngle;
2133
1914
  if (Math.abs(deltaAng) > TWO_PI) {
2134
- // draw only full circle if more than that is specified
2135
1915
  deltaAng = TWO_PI;
2136
1916
  } else if (deltaAng !== 0 && anticlockwise !== deltaAng < 0) {
2137
- // necessary to flip direction of rendering
2138
1917
  const dir = anticlockwise ? -1 : 1;
2139
1918
  deltaAng = dir * TWO_PI + deltaAng;
2140
1919
  }
@@ -2142,38 +1921,21 @@ var VectorMixin = {
2142
1921
  const segAng = deltaAng / numSegs;
2143
1922
  const handleLen = segAng / HALF_PI * KAPPA * radius;
2144
1923
  let curAng = startAngle;
2145
-
2146
- // component distances between anchor point and control point
2147
1924
  let deltaCx = -Math.sin(curAng) * handleLen;
2148
1925
  let deltaCy = Math.cos(curAng) * handleLen;
2149
-
2150
- // anchor point
2151
1926
  let ax = x + Math.cos(curAng) * radius;
2152
1927
  let ay = y + Math.sin(curAng) * radius;
2153
-
2154
- // calculate and render segments
2155
1928
  this.moveTo(ax, ay);
2156
1929
  for (let segIdx = 0; segIdx < numSegs; segIdx++) {
2157
- // starting control point
2158
1930
  const cp1x = ax + deltaCx;
2159
1931
  const cp1y = ay + deltaCy;
2160
-
2161
- // step angle
2162
1932
  curAng += segAng;
2163
-
2164
- // next anchor point
2165
1933
  ax = x + Math.cos(curAng) * radius;
2166
1934
  ay = y + Math.sin(curAng) * radius;
2167
-
2168
- // next control point delta
2169
1935
  deltaCx = -Math.sin(curAng) * handleLen;
2170
1936
  deltaCy = Math.cos(curAng) * handleLen;
2171
-
2172
- // ending control point
2173
1937
  const cp2x = ax - deltaCx;
2174
1938
  const cp2y = ay - deltaCy;
2175
-
2176
- // render segment
2177
1939
  this.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, ax, ay);
2178
1940
  }
2179
1941
  return this;
@@ -2237,9 +1999,7 @@ var VectorMixin = {
2237
1999
  return this.addContent(`W${this._windingRule(rule)} n`);
2238
2000
  },
2239
2001
  transform(m11, m12, m21, m22, dx, dy) {
2240
- // keep track of the current transformation matrix
2241
2002
  if (m11 === 1 && m12 === 0 && m21 === 0 && m22 === 1 && dx === 0 && dy === 0) {
2242
- // Ignore identity transforms
2243
2003
  return this;
2244
2004
  }
2245
2005
  const m = this._ctm;
@@ -2405,7 +2165,6 @@ class AFMFont {
2405
2165
  this.boundingBoxes = {};
2406
2166
  this.kernPairs = {};
2407
2167
  this.parse();
2408
- // todo: remove charWidths since appears to not be used
2409
2168
  this.charWidths = new Array(256);
2410
2169
  for (let char = 0; char <= 255; char++) {
2411
2170
  this.charWidths[char] = this.glyphWidths[characters[char]];
@@ -2512,21 +2271,18 @@ class PDFFont {
2512
2271
  return;
2513
2272
  }
2514
2273
  this.embed();
2515
- return this.embedded = true;
2274
+ this.embedded = true;
2516
2275
  }
2517
2276
  embed() {
2518
2277
  throw new Error('Must be implemented by subclasses');
2519
2278
  }
2520
- lineHeight(size, includeGap) {
2521
- if (includeGap == null) {
2522
- includeGap = false;
2523
- }
2279
+ lineHeight(size) {
2280
+ let includeGap = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
2524
2281
  const gap = includeGap ? this.lineGap : 0;
2525
2282
  return (this.ascender + gap - this.descender) / 1000 * size;
2526
2283
  }
2527
2284
  }
2528
2285
 
2529
- // This insanity is so bundlers can inline the font files
2530
2286
  const STANDARD_FONTS = {
2531
2287
  Courier() {
2532
2288
  return fs.readFileSync(__dirname + '/data/Courier.afm', 'utf8');
@@ -2654,8 +2410,6 @@ class EmbeddedFont extends PDFFont {
2654
2410
  }
2655
2411
  layoutRun(text, features) {
2656
2412
  const run = this.font.layout(text, features);
2657
-
2658
- // Normalize position values
2659
2413
  for (let i = 0; i < run.positions.length; i++) {
2660
2414
  const position = run.positions[i];
2661
2415
  for (let key in position) {
@@ -2678,16 +2432,12 @@ class EmbeddedFont extends PDFFont {
2678
2432
  return run;
2679
2433
  }
2680
2434
  layout(text, features, onlyWidth) {
2681
- // Skip the cache if any user defined features are applied
2682
2435
  if (features) {
2683
2436
  return this.layoutRun(text, features);
2684
2437
  }
2685
2438
  let glyphs = onlyWidth ? null : [];
2686
2439
  let positions = onlyWidth ? null : [];
2687
2440
  let advanceWidth = 0;
2688
-
2689
- // Split the string by words to increase cache efficiency.
2690
- // For this purpose, spaces and tabs are a good enough delimeter.
2691
2441
  let last = 0;
2692
2442
  let index = 0;
2693
2443
  while (index <= text.length) {
@@ -2749,17 +2499,15 @@ class EmbeddedFont extends PDFFont {
2749
2499
  if (1 <= familyClass && familyClass <= 7) {
2750
2500
  flags |= 1 << 1;
2751
2501
  }
2752
- flags |= 1 << 2; // assume the font uses non-latin characters
2502
+ flags |= 1 << 2;
2753
2503
  if (familyClass === 10) {
2754
2504
  flags |= 1 << 3;
2755
2505
  }
2756
2506
  if (this.font.head.macStyle.italic) {
2757
2507
  flags |= 1 << 6;
2758
2508
  }
2759
-
2760
- // generate a tag (6 uppercase letters. 17 is the char code offset from '0' to 'A'. 73 will map to 'Z')
2761
2509
  const tag = [1, 2, 3, 4, 5, 6].map(i => String.fromCharCode((this.id.charCodeAt(i) || 73) + 17)).join('');
2762
- const name = tag + '+' + this.font.postscriptName.replaceAll(' ', '_');
2510
+ const name = tag + '+' + this.font.postscriptName?.replaceAll(' ', '_');
2763
2511
  const {
2764
2512
  bbox
2765
2513
  } = this.font;
@@ -2774,8 +2522,7 @@ class EmbeddedFont extends PDFFont {
2774
2522
  CapHeight: (this.font.capHeight || this.font.ascent) * this.scale,
2775
2523
  XHeight: (this.font.xHeight || 0) * this.scale,
2776
2524
  StemV: 0
2777
- }); // not sure how to calculate this
2778
-
2525
+ });
2779
2526
  if (isCFF) {
2780
2527
  descriptor.data.FontFile3 = fontFile;
2781
2528
  } else {
@@ -2817,17 +2564,11 @@ class EmbeddedFont extends PDFFont {
2817
2564
  };
2818
2565
  return this.dictionary.end();
2819
2566
  }
2820
-
2821
- // Maps the glyph ids encoded in the PDF back to unicode strings
2822
- // Because of ligature substitutions and the like, there may be one or more
2823
- // unicode characters represented by each glyph.
2824
2567
  toUnicodeCmap() {
2825
2568
  const cmap = this.document.ref();
2826
2569
  const entries = [];
2827
2570
  for (let codePoints of this.unicode) {
2828
2571
  const encoded = [];
2829
-
2830
- // encode codePoints to utf16
2831
2572
  for (let value of codePoints) {
2832
2573
  if (value > 0xffff) {
2833
2574
  value -= 0x10000;
@@ -2894,12 +2635,9 @@ class PDFFontFactory {
2894
2635
  }
2895
2636
 
2896
2637
  const isEqualFont = (font1, font2) => {
2897
- // compare font checksum
2898
2638
  if (font1.font._tables?.head?.checkSumAdjustment !== font2.font._tables?.head?.checkSumAdjustment) {
2899
2639
  return false;
2900
2640
  }
2901
-
2902
- // compare font name table
2903
2641
  if (JSON.stringify(font1.font._tables?.name?.records) !== JSON.stringify(font2.font._tables?.name?.records)) {
2904
2642
  return false;
2905
2643
  }
@@ -2908,18 +2646,18 @@ const isEqualFont = (font1, font2) => {
2908
2646
  var FontsMixin = {
2909
2647
  initFonts() {
2910
2648
  let defaultFont = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'Helvetica';
2911
- // Lookup table for embedded fonts
2649
+ let defaultFontFamily = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
2650
+ let defaultFontSize = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 12;
2912
2651
  this._fontFamilies = {};
2913
2652
  this._fontCount = 0;
2914
-
2915
- // Font state
2916
- this._fontSize = 12;
2653
+ this._fontSource = defaultFont;
2654
+ this._fontFamily = defaultFontFamily;
2655
+ this._fontSize = defaultFontSize;
2917
2656
  this._font = null;
2657
+ this._remSize = defaultFontSize;
2918
2658
  this._registeredFonts = {};
2919
-
2920
- // Set the default font
2921
2659
  if (defaultFont) {
2922
- this.font(defaultFont);
2660
+ this.font(defaultFont, defaultFontFamily);
2923
2661
  }
2924
2662
  },
2925
2663
  font(src, family, size) {
@@ -2928,8 +2666,6 @@ var FontsMixin = {
2928
2666
  size = family;
2929
2667
  family = null;
2930
2668
  }
2931
-
2932
- // check registered fonts if src is a string
2933
2669
  if (typeof src === 'string' && this._registeredFonts[src]) {
2934
2670
  cacheKey = src;
2935
2671
  ({
@@ -2942,28 +2678,21 @@ var FontsMixin = {
2942
2678
  cacheKey = null;
2943
2679
  }
2944
2680
  }
2681
+ this._fontSource = src;
2682
+ this._fontFamily = family;
2945
2683
  if (size != null) {
2946
2684
  this.fontSize(size);
2947
2685
  }
2948
-
2949
- // fast path: check if the font is already in the PDF
2950
2686
  if (font = this._fontFamilies[cacheKey]) {
2951
2687
  this._font = font;
2952
2688
  return this;
2953
2689
  }
2954
-
2955
- // load the font
2956
2690
  const id = `F${++this._fontCount}`;
2957
2691
  this._font = PDFFontFactory.open(this, src, family, id);
2958
-
2959
- // check for existing font familes with the same name already in the PDF
2960
- // useful if the font was passed as a buffer
2961
2692
  if ((font = this._fontFamilies[this._font.name]) && isEqualFont(this._font, font)) {
2962
2693
  this._font = font;
2963
2694
  return this;
2964
2695
  }
2965
-
2966
- // save the font for reuse later
2967
2696
  if (cacheKey) {
2968
2697
  this._fontFamilies[cacheKey] = this._font;
2969
2698
  }
@@ -2973,13 +2702,10 @@ var FontsMixin = {
2973
2702
  return this;
2974
2703
  },
2975
2704
  fontSize(_fontSize) {
2976
- this._fontSize = _fontSize;
2705
+ this._fontSize = this.sizeToPoint(_fontSize);
2977
2706
  return this;
2978
2707
  },
2979
2708
  currentLineHeight(includeGap) {
2980
- if (includeGap == null) {
2981
- includeGap = false;
2982
- }
2983
2709
  return this._font.lineHeight(this._fontSize, includeGap);
2984
2710
  },
2985
2711
  registerFont(name, src, family) {
@@ -2988,6 +2714,67 @@ var FontsMixin = {
2988
2714
  family
2989
2715
  };
2990
2716
  return this;
2717
+ },
2718
+ sizeToPoint(size) {
2719
+ let defaultValue = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
2720
+ let page = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.page;
2721
+ let percentageWidth = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : undefined;
2722
+ if (!percentageWidth) percentageWidth = this._fontSize;
2723
+ if (typeof defaultValue !== 'number') defaultValue = this.sizeToPoint(defaultValue);
2724
+ if (size === undefined) return defaultValue;
2725
+ if (typeof size === 'number') return size;
2726
+ if (typeof size === 'boolean') return Number(size);
2727
+ const match = String(size).match(/((\d+)?(\.\d+)?)(em|in|px|cm|mm|pc|ex|ch|rem|vw|vh|vmin|vmax|%|pt)?/);
2728
+ if (!match) throw new Error(`Unsupported size '${size}'`);
2729
+ let multiplier;
2730
+ switch (match[4]) {
2731
+ case 'em':
2732
+ multiplier = this._fontSize;
2733
+ break;
2734
+ case 'in':
2735
+ multiplier = IN_TO_PT;
2736
+ break;
2737
+ case 'px':
2738
+ multiplier = PX_TO_IN * IN_TO_PT;
2739
+ break;
2740
+ case 'cm':
2741
+ multiplier = CM_TO_IN * IN_TO_PT;
2742
+ break;
2743
+ case 'mm':
2744
+ multiplier = MM_TO_CM * CM_TO_IN * IN_TO_PT;
2745
+ break;
2746
+ case 'pc':
2747
+ multiplier = PC_TO_PT;
2748
+ break;
2749
+ case 'ex':
2750
+ multiplier = this.currentLineHeight();
2751
+ break;
2752
+ case 'ch':
2753
+ multiplier = this.widthOfString('0');
2754
+ break;
2755
+ case 'rem':
2756
+ multiplier = this._remSize;
2757
+ break;
2758
+ case 'vw':
2759
+ multiplier = page.width / 100;
2760
+ break;
2761
+ case 'vh':
2762
+ multiplier = page.height / 100;
2763
+ break;
2764
+ case 'vmin':
2765
+ multiplier = Math.min(page.width, page.height) / 100;
2766
+ break;
2767
+ case 'vmax':
2768
+ multiplier = Math.max(page.width, page.height) / 100;
2769
+ break;
2770
+ case '%':
2771
+ multiplier = percentageWidth / 100;
2772
+ break;
2773
+ case 'pt':
2774
+ default:
2775
+ multiplier = 1;
2776
+ }
2777
+ return multiplier * Number(match[1]);
2991
2778
  }
2992
2779
  };
2993
2780
 
@@ -3002,7 +2789,7 @@ class LineWrapper extends EventEmitter {
3002
2789
  this.characterSpacing = (options.characterSpacing || 0) * this.horizontalScaling / 100;
3003
2790
  this.wordSpacing = (options.wordSpacing === 0) * this.horizontalScaling / 100;
3004
2791
  this.columns = options.columns || 1;
3005
- this.columnGap = (options.columnGap != null ? options.columnGap : 18) * this.horizontalScaling / 100; // 1/4 inch
2792
+ this.columnGap = (options.columnGap != null ? options.columnGap : 18) * this.horizontalScaling / 100;
3006
2793
  this.lineWidth = (options.width * this.horizontalScaling / 100 - this.columnGap * (this.columns - 1)) / this.columns;
3007
2794
  this.spaceLeft = this.lineWidth;
3008
2795
  this.startX = this.document.x;
@@ -3011,44 +2798,30 @@ class LineWrapper extends EventEmitter {
3011
2798
  this.ellipsis = options.ellipsis;
3012
2799
  this.continuedX = 0;
3013
2800
  this.features = options.features;
3014
-
3015
- // calculate the maximum Y position the text can appear at
3016
2801
  if (options.height != null) {
3017
2802
  this.height = options.height;
3018
- this.maxY = this.startY + options.height;
2803
+ this.maxY = PDFNumber(this.startY + options.height);
3019
2804
  } else {
3020
- this.maxY = this.document.page.maxY();
2805
+ this.maxY = PDFNumber(this.document.page.maxY());
3021
2806
  }
3022
-
3023
- // handle paragraph indents
3024
2807
  this.on('firstLine', options => {
3025
- // if this is the first line of the text segment, and
3026
- // we're continuing where we left off, indent that much
3027
- // otherwise use the user specified indent option
3028
2808
  const indent = this.continuedX || this.indent;
3029
2809
  this.document.x += indent;
3030
2810
  this.lineWidth -= indent;
3031
-
3032
- // if indentAllLines is set to true
3033
- // we're not resetting the indentation for this paragraph after the first line
3034
2811
  if (options.indentAllLines) {
3035
2812
  return;
3036
2813
  }
3037
-
3038
- // otherwise we start the next line without indent
3039
- return this.once('line', () => {
2814
+ this.once('line', () => {
3040
2815
  this.document.x -= indent;
3041
2816
  this.lineWidth += indent;
3042
2817
  if (options.continued && !this.continuedX) {
3043
2818
  this.continuedX = this.indent;
3044
2819
  }
3045
2820
  if (!options.continued) {
3046
- return this.continuedX = 0;
2821
+ this.continuedX = 0;
3047
2822
  }
3048
2823
  });
3049
2824
  });
3050
-
3051
- // handle left aligning last lines of paragraphs
3052
2825
  this.on('lastLine', options => {
3053
2826
  const {
3054
2827
  align
@@ -3057,7 +2830,7 @@ class LineWrapper extends EventEmitter {
3057
2830
  options.align = 'left';
3058
2831
  }
3059
2832
  this.lastLine = true;
3060
- return this.once('line', () => {
2833
+ this.once('line', () => {
3061
2834
  this.document.y += options.paragraphGap || 0;
3062
2835
  options.align = align;
3063
2836
  return this.lastLine = false;
@@ -3074,7 +2847,6 @@ class LineWrapper extends EventEmitter {
3074
2847
  return w + this.wordWidth(HYPHEN) <= this.spaceLeft;
3075
2848
  }
3076
2849
  eachWord(text, fn) {
3077
- // setup a unicode line breaker
3078
2850
  let bk;
3079
2851
  const breaker = new LineBreaker(text);
3080
2852
  let last = null;
@@ -3083,19 +2855,12 @@ class LineWrapper extends EventEmitter {
3083
2855
  var shouldContinue;
3084
2856
  let word = text.slice((last != null ? last.position : undefined) || 0, bk.position);
3085
2857
  let w = wordWidths[word] != null ? wordWidths[word] : wordWidths[word] = this.wordWidth(word);
3086
-
3087
- // if the word is longer than the whole line, chop it up
3088
- // TODO: break by grapheme clusters, not JS string characters
3089
2858
  if (w > this.lineWidth + this.continuedX) {
3090
- // make some fake break objects
3091
2859
  let lbk = last;
3092
2860
  const fbk = {};
3093
2861
  while (word.length) {
3094
- // fit as much of the word as possible into the space we have
3095
2862
  var l, mightGrow;
3096
2863
  if (w > this.spaceLeft) {
3097
- // start our check at the end of our available space - this method is faster than a loop of each character and it resolves
3098
- // an issue with long loops when processing massive words, such as a huge number of spaces
3099
2864
  l = Math.ceil(this.spaceLeft / (w / word.length));
3100
2865
  w = this.wordWidth(word.slice(0, l));
3101
2866
  mightGrow = w <= this.spaceLeft && l < word.length;
@@ -3103,7 +2868,6 @@ class LineWrapper extends EventEmitter {
3103
2868
  l = word.length;
3104
2869
  }
3105
2870
  let mustShrink = w > this.spaceLeft && l > 0;
3106
- // shrink or grow word as necessary after our near-guess above
3107
2871
  while (mustShrink || mightGrow) {
3108
2872
  if (mustShrink) {
3109
2873
  w = this.wordWidth(word.slice(0, --l));
@@ -3114,20 +2878,14 @@ class LineWrapper extends EventEmitter {
3114
2878
  mightGrow = w <= this.spaceLeft && l < word.length;
3115
2879
  }
3116
2880
  }
3117
-
3118
- // check for the edge case where a single character cannot fit into a line.
3119
2881
  if (l === 0 && this.spaceLeft === this.lineWidth) {
3120
2882
  l = 1;
3121
2883
  }
3122
-
3123
- // send a required break unless this is the last piece and a linebreak is not specified
3124
2884
  fbk.required = bk.required || l < word.length;
3125
2885
  shouldContinue = fn(word.slice(0, l), w, fbk, lbk);
3126
2886
  lbk = {
3127
2887
  required: false
3128
2888
  };
3129
-
3130
- // get the remaining piece of the word
3131
2889
  word = word.slice(l);
3132
2890
  w = this.wordWidth(word);
3133
2891
  if (shouldContinue === false) {
@@ -3135,7 +2893,6 @@ class LineWrapper extends EventEmitter {
3135
2893
  }
3136
2894
  }
3137
2895
  } else {
3138
- // otherwise just emit the break as it was given to us
3139
2896
  shouldContinue = fn(word, w, bk, last);
3140
2897
  }
3141
2898
  if (shouldContinue === false) {
@@ -3145,7 +2902,6 @@ class LineWrapper extends EventEmitter {
3145
2902
  }
3146
2903
  }
3147
2904
  wrap(text, options) {
3148
- // override options from previous continued fragments
3149
2905
  this.horizontalScaling = options.horizontalScaling || 100;
3150
2906
  if (options.indent != null) {
3151
2907
  this.indent = options.indent * this.horizontalScaling / 100;
@@ -3159,10 +2915,6 @@ class LineWrapper extends EventEmitter {
3159
2915
  if (options.ellipsis != null) {
3160
2916
  this.ellipsis = options.ellipsis;
3161
2917
  }
3162
-
3163
- // make sure we're actually on the page
3164
- // and that the first line of is never by
3165
- // itself at the bottom of a page (orphans)
3166
2918
  const nextY = this.document.y + this.document.currentLineHeight(true);
3167
2919
  if (this.document.y > this.maxY || nextY > this.maxY) {
3168
2920
  this.nextSection();
@@ -3173,7 +2925,7 @@ class LineWrapper extends EventEmitter {
3173
2925
  let lc = 0;
3174
2926
  let {
3175
2927
  y
3176
- } = this.document; // used to reset Y pos if options.continued (below)
2928
+ } = this.document;
3177
2929
  const emitLine = () => {
3178
2930
  options.textWidth = textWidth + this.wordSpacing * (wc - 1);
3179
2931
  options.wordCount = wc;
@@ -3196,23 +2948,17 @@ class LineWrapper extends EventEmitter {
3196
2948
  wc++;
3197
2949
  }
3198
2950
  if (bk.required || !this.canFit(word, w)) {
3199
- // if the user specified a max height and an ellipsis, and is about to pass the
3200
- // max height and max columns after the next line, append the ellipsis
3201
2951
  const lh = this.document.currentLineHeight(true);
3202
- if (this.height != null && this.ellipsis && this.document.y + lh * 2 > this.maxY && this.column >= this.columns) {
2952
+ if (this.height != null && this.ellipsis && PDFNumber(this.document.y + lh * 2) > this.maxY && this.column >= this.columns) {
3203
2953
  if (this.ellipsis === true) {
3204
2954
  this.ellipsis = '…';
3205
- } // map default ellipsis character
2955
+ }
3206
2956
  buffer = buffer.replace(/\s+$/, '');
3207
2957
  textWidth = this.wordWidth(buffer + this.ellipsis);
3208
-
3209
- // remove characters from the buffer until the ellipsis fits
3210
- // to avoid infinite loop need to stop while-loop if buffer is empty string
3211
2958
  while (buffer && textWidth > this.lineWidth) {
3212
2959
  buffer = buffer.slice(0, -1).replace(/\s+$/, '');
3213
2960
  textWidth = this.wordWidth(buffer + this.ellipsis);
3214
2961
  }
3215
- // need to add ellipsis only if there is enough space for it
3216
2962
  if (textWidth <= this.lineWidth) {
3217
2963
  buffer = buffer + this.ellipsis;
3218
2964
  }
@@ -3227,35 +2973,25 @@ class LineWrapper extends EventEmitter {
3227
2973
  }
3228
2974
  this.emit('lastLine', options, this);
3229
2975
  }
3230
-
3231
- // Previous entry is a soft hyphen - add visible hyphen.
3232
2976
  if (buffer[buffer.length - 1] == SOFT_HYPHEN) {
3233
2977
  buffer = buffer.slice(0, -1) + HYPHEN;
3234
2978
  this.spaceLeft -= this.wordWidth(HYPHEN);
3235
2979
  }
3236
2980
  emitLine();
3237
-
3238
- // if we've reached the edge of the page,
3239
- // continue on a new page or column
3240
- if (this.document.y + lh > this.maxY) {
2981
+ if (PDFNumber(this.document.y + lh) > this.maxY) {
3241
2982
  const shouldContinue = this.nextSection();
3242
-
3243
- // stop if we reached the maximum height
3244
2983
  if (!shouldContinue) {
3245
2984
  wc = 0;
3246
2985
  buffer = '';
3247
2986
  return false;
3248
2987
  }
3249
2988
  }
3250
-
3251
- // reset the space left and buffer
3252
2989
  if (bk.required) {
3253
2990
  this.spaceLeft = this.lineWidth;
3254
2991
  buffer = '';
3255
2992
  textWidth = 0;
3256
2993
  return wc = 0;
3257
2994
  } else {
3258
- // reset the space left and buffer
3259
2995
  this.spaceLeft = this.lineWidth - w;
3260
2996
  buffer = word;
3261
2997
  textWidth = w;
@@ -3270,25 +3006,19 @@ class LineWrapper extends EventEmitter {
3270
3006
  emitLine();
3271
3007
  }
3272
3008
  this.emit('sectionEnd', options, this);
3273
-
3274
- // if the wrap is set to be continued, save the X position
3275
- // to start the first line of the next segment at, and reset
3276
- // the y position
3277
3009
  if (options.continued === true) {
3278
3010
  if (lc > 1) {
3279
3011
  this.continuedX = 0;
3280
3012
  }
3281
3013
  this.continuedX += options.textWidth || 0;
3282
- return this.document.y = y;
3014
+ this.document.y = y;
3283
3015
  } else {
3284
- return this.document.x = this.startX;
3016
+ this.document.x = this.startX;
3285
3017
  }
3286
3018
  }
3287
3019
  nextSection(options) {
3288
3020
  this.emit('sectionEnd', options, this);
3289
3021
  if (++this.column > this.columns) {
3290
- // if a max height was specified by the user, we're done.
3291
- // otherwise, the default is to make a new page at the bottom.
3292
3022
  if (this.height != null) {
3293
3023
  return false;
3294
3024
  }
@@ -3317,10 +3047,9 @@ const {
3317
3047
  var TextMixin = {
3318
3048
  initText() {
3319
3049
  this._line = this._line.bind(this);
3320
- // Current coordinates
3321
3050
  this.x = 0;
3322
3051
  this.y = 0;
3323
- return this._lineGap = 0;
3052
+ this._lineGap = 0;
3324
3053
  },
3325
3054
  lineGap(_lineGap) {
3326
3055
  this._lineGap = _lineGap;
@@ -3342,11 +3071,7 @@ var TextMixin = {
3342
3071
  },
3343
3072
  _text(text, x, y, options, lineCallback) {
3344
3073
  options = this._initOptions(x, y, options);
3345
-
3346
- // Convert text to a string
3347
3074
  text = text == null ? '' : `${text}`;
3348
-
3349
- // if the wordSpacing option is specified, remove multiple consecutive spaces
3350
3075
  if (options.wordSpacing) {
3351
3076
  text = text.replace(/\s{2,}/g, ' ');
3352
3077
  }
@@ -3355,8 +3080,12 @@ var TextMixin = {
3355
3080
  options.structParent.add(this.struct(options.structType || 'P', [this.markStructureContent(options.structType || 'P')]));
3356
3081
  }
3357
3082
  };
3358
-
3359
- // word wrapping
3083
+ if (options.rotation !== 0) {
3084
+ this.save();
3085
+ this.rotate(-options.rotation, {
3086
+ origin: [this.x, this.y]
3087
+ });
3088
+ }
3360
3089
  if (options.width) {
3361
3090
  let wrapper = this._wrapper;
3362
3091
  if (!wrapper) {
@@ -3367,14 +3096,13 @@ var TextMixin = {
3367
3096
  this._wrapper = options.continued ? wrapper : null;
3368
3097
  this._textOptions = options.continued ? options : null;
3369
3098
  wrapper.wrap(text, options);
3370
-
3371
- // render paragraphs as single lines
3372
3099
  } else {
3373
3100
  for (let line of text.split('\n')) {
3374
3101
  addStructure();
3375
3102
  lineCallback(line, options);
3376
3103
  }
3377
3104
  }
3105
+ if (options.rotation !== 0) this.restore();
3378
3106
  return this;
3379
3107
  },
3380
3108
  text(text, x, y, options) {
@@ -3385,17 +3113,108 @@ var TextMixin = {
3385
3113
  const horizontalScaling = options.horizontalScaling || 100;
3386
3114
  return (this._font.widthOfString(string, this._fontSize, options.features) + (options.characterSpacing || 0) * (string.length - 1)) * horizontalScaling / 100;
3387
3115
  },
3116
+ boundsOfString(string, x, y, options) {
3117
+ options = this._initOptions(x, y, options);
3118
+ ({
3119
+ x,
3120
+ y
3121
+ } = this);
3122
+ const lineGap = options.lineGap ?? this._lineGap ?? 0;
3123
+ const lineHeight = this.currentLineHeight(true) + lineGap;
3124
+ let contentWidth = 0;
3125
+ string = String(string ?? '');
3126
+ if (options.wordSpacing) {
3127
+ string = string.replace(/\s{2,}/g, ' ');
3128
+ }
3129
+ if (options.width) {
3130
+ let wrapper = new LineWrapper(this, options);
3131
+ wrapper.on('line', (text, options) => {
3132
+ this.y += lineHeight;
3133
+ text = text.replace(/\n/g, '');
3134
+ if (text.length) {
3135
+ let wordSpacing = options.wordSpacing ?? 0;
3136
+ const characterSpacing = options.characterSpacing ?? 0;
3137
+ if (options.width && options.align === 'justify') {
3138
+ const words = text.trim().split(/\s+/);
3139
+ const textWidth = this.widthOfString(text.replace(/\s+/g, ''), options);
3140
+ const spaceWidth = this.widthOfString(' ') + characterSpacing;
3141
+ wordSpacing = Math.max(0, (options.lineWidth - textWidth) / Math.max(1, words.length - 1) - spaceWidth);
3142
+ }
3143
+ contentWidth = Math.max(contentWidth, options.textWidth + wordSpacing * (options.wordCount - 1) + characterSpacing * (text.length - 1));
3144
+ }
3145
+ });
3146
+ wrapper.wrap(string, options);
3147
+ } else {
3148
+ for (let line of string.split('\n')) {
3149
+ const lineWidth = this.widthOfString(line, options);
3150
+ this.y += lineHeight;
3151
+ contentWidth = Math.max(contentWidth, lineWidth);
3152
+ }
3153
+ }
3154
+ let contentHeight = this.y - y;
3155
+ if (options.height) contentHeight = Math.min(contentHeight, options.height);
3156
+ this.x = x;
3157
+ this.y = y;
3158
+ if (options.rotation === 0) {
3159
+ return {
3160
+ x,
3161
+ y,
3162
+ width: contentWidth,
3163
+ height: contentHeight
3164
+ };
3165
+ } else if (options.rotation === 90) {
3166
+ return {
3167
+ x: x,
3168
+ y: y - contentWidth,
3169
+ width: contentHeight,
3170
+ height: contentWidth
3171
+ };
3172
+ } else if (options.rotation === 180) {
3173
+ return {
3174
+ x: x - contentWidth,
3175
+ y: y - contentHeight,
3176
+ width: contentWidth,
3177
+ height: contentHeight
3178
+ };
3179
+ } else if (options.rotation === 270) {
3180
+ return {
3181
+ x: x - contentHeight,
3182
+ y: y,
3183
+ width: contentHeight,
3184
+ height: contentWidth
3185
+ };
3186
+ }
3187
+ const cos = cosine(options.rotation);
3188
+ const sin = sine(options.rotation);
3189
+ const x1 = x;
3190
+ const y1 = y;
3191
+ const x2 = x + contentWidth * cos;
3192
+ const y2 = y - contentWidth * sin;
3193
+ const x3 = x + contentWidth * cos + contentHeight * sin;
3194
+ const y3 = y - contentWidth * sin + contentHeight * cos;
3195
+ const x4 = x + contentHeight * sin;
3196
+ const y4 = y + contentHeight * cos;
3197
+ const xMin = Math.min(x1, x2, x3, x4);
3198
+ const xMax = Math.max(x1, x2, x3, x4);
3199
+ const yMin = Math.min(y1, y2, y3, y4);
3200
+ const yMax = Math.max(y1, y2, y3, y4);
3201
+ return {
3202
+ x: xMin,
3203
+ y: yMin,
3204
+ width: xMax - xMin,
3205
+ height: yMax - yMin
3206
+ };
3207
+ },
3388
3208
  heightOfString(text, options) {
3389
3209
  const {
3390
3210
  x,
3391
3211
  y
3392
3212
  } = this;
3393
3213
  options = this._initOptions(options);
3394
- options.height = Infinity; // don't break pages
3395
-
3214
+ options.height = Infinity;
3396
3215
  const lineGap = options.lineGap || this._lineGap || 0;
3397
3216
  this._text(text, this.x, this.y, options, () => {
3398
- return this.y += this.currentLineHeight(true) + lineGap;
3217
+ this.y += this.currentLineHeight(true) + lineGap;
3399
3218
  });
3400
3219
  const height = this.y - y;
3401
3220
  this.x = x;
@@ -3493,12 +3312,12 @@ var TextMixin = {
3493
3312
  wrapper.on('sectionStart', () => {
3494
3313
  const pos = indent + itemIndent * (level - 1);
3495
3314
  this.x += pos;
3496
- return wrapper.lineWidth -= pos;
3315
+ wrapper.lineWidth -= pos;
3497
3316
  });
3498
3317
  wrapper.on('sectionEnd', () => {
3499
3318
  const pos = indent + itemIndent * (level - 1);
3500
3319
  this.x -= pos;
3501
- return wrapper.lineWidth += pos;
3320
+ wrapper.lineWidth += pos;
3502
3321
  });
3503
3322
  wrapper.wrap(listItem, options);
3504
3323
  };
@@ -3515,11 +3334,7 @@ var TextMixin = {
3515
3334
  options = x;
3516
3335
  x = null;
3517
3336
  }
3518
-
3519
- // clone options object
3520
3337
  const result = Object.assign({}, options);
3521
-
3522
- // extend options with previous values for continued text
3523
3338
  if (this._textOptions) {
3524
3339
  for (let key in this._textOptions) {
3525
3340
  const val = this._textOptions[key];
@@ -3530,16 +3345,12 @@ var TextMixin = {
3530
3345
  }
3531
3346
  }
3532
3347
  }
3533
-
3534
- // Update the current position
3535
3348
  if (x != null) {
3536
3349
  this.x = x;
3537
3350
  }
3538
3351
  if (y != null) {
3539
3352
  this.y = y;
3540
3353
  }
3541
-
3542
- // wrap to margins if no x or y position passed
3543
3354
  if (result.lineBreak !== false) {
3544
3355
  if (result.width == null) {
3545
3356
  result.width = this.page.width - this.x - this.page.margins.right;
@@ -3551,8 +3362,9 @@ var TextMixin = {
3551
3362
  }
3552
3363
  if (result.columnGap == null) {
3553
3364
  result.columnGap = 18;
3554
- } // 1/4 inch
3555
-
3365
+ }
3366
+ result.rotation = Number(options.rotation ?? 0) % 360;
3367
+ if (result.rotation < 0) result.rotation += 360;
3556
3368
  return result;
3557
3369
  },
3558
3370
  _line(text) {
@@ -3561,9 +3373,9 @@ var TextMixin = {
3561
3373
  this._fragment(text, this.x, this.y, options);
3562
3374
  const lineGap = options.lineGap || this._lineGap || 0;
3563
3375
  if (!wrapper) {
3564
- return this.x += this.widthOfString(text, options);
3376
+ this.x += this.widthOfString(text, options);
3565
3377
  } else {
3566
- return this.y += this.currentLineHeight(true) + lineGap;
3378
+ this.y += this.currentLineHeight(true) + lineGap;
3567
3379
  }
3568
3380
  },
3569
3381
  _fragment(text, x, y, options) {
@@ -3572,14 +3384,10 @@ var TextMixin = {
3572
3384
  if (text.length === 0) {
3573
3385
  return;
3574
3386
  }
3575
-
3576
- // handle options
3577
3387
  const align = options.align || 'left';
3578
3388
  let wordSpacing = options.wordSpacing || 0;
3579
3389
  const characterSpacing = options.characterSpacing || 0;
3580
3390
  const horizontalScaling = options.horizontalScaling || 100;
3581
-
3582
- // text alignments
3583
3391
  if (options.width) {
3584
3392
  switch (align) {
3585
3393
  case 'right':
@@ -3590,7 +3398,6 @@ var TextMixin = {
3590
3398
  x += options.lineWidth / 2 - options.textWidth / 2;
3591
3399
  break;
3592
3400
  case 'justify':
3593
- // calculate the word spacing value
3594
3401
  words = text.trim().split(/\s+/);
3595
3402
  textWidth = this.widthOfString(text.replace(/\s+/g, ''), options);
3596
3403
  var spaceWidth = this.widthOfString(' ') + characterSpacing;
@@ -3598,8 +3405,6 @@ var TextMixin = {
3598
3405
  break;
3599
3406
  }
3600
3407
  }
3601
-
3602
- // text baseline alignments based on http://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling
3603
3408
  if (typeof options.baseline === 'number') {
3604
3409
  dy = -options.baseline;
3605
3410
  } else {
@@ -3632,11 +3437,7 @@ var TextMixin = {
3632
3437
  }
3633
3438
  dy = dy / 1000 * this._fontSize;
3634
3439
  }
3635
-
3636
- // calculate the actual rendered width of the string after word and character spacing
3637
3440
  const renderedWidth = options.textWidth + wordSpacing * (options.wordCount - 1) + characterSpacing * (text.length - 1);
3638
-
3639
- // create link annotations if the link option is given
3640
3441
  if (options.link != null) {
3641
3442
  this.link(x, y, renderedWidth, this.currentLineHeight(), options.link);
3642
3443
  }
@@ -3646,8 +3447,6 @@ var TextMixin = {
3646
3447
  if (options.destination != null) {
3647
3448
  this.addNamedDestination(options.destination, 'XYZ', x, y, null);
3648
3449
  }
3649
-
3650
- // create underline
3651
3450
  if (options.underline) {
3652
3451
  this.save();
3653
3452
  if (!options.stroke) {
@@ -3661,8 +3460,6 @@ var TextMixin = {
3661
3460
  this.stroke();
3662
3461
  this.restore();
3663
3462
  }
3664
-
3665
- // create strikethrough line
3666
3463
  if (options.strike) {
3667
3464
  this.save();
3668
3465
  if (!options.stroke) {
@@ -3677,8 +3474,6 @@ var TextMixin = {
3677
3474
  this.restore();
3678
3475
  }
3679
3476
  this.save();
3680
-
3681
- // oblique (angle in degrees or boolean)
3682
3477
  if (options.oblique) {
3683
3478
  let skew;
3684
3479
  if (typeof options.oblique === 'number') {
@@ -3690,45 +3485,24 @@ var TextMixin = {
3690
3485
  this.transform(1, 0, skew, 1, -skew * dy, 0);
3691
3486
  this.transform(1, 0, 0, 1, -x, -y);
3692
3487
  }
3693
-
3694
- // flip coordinate system
3695
3488
  this.transform(1, 0, 0, -1, 0, this.page.height);
3696
3489
  y = this.page.height - y - dy;
3697
-
3698
- // add current font to page if necessary
3699
3490
  if (this.page.fonts[this._font.id] == null) {
3700
3491
  this.page.fonts[this._font.id] = this._font.ref();
3701
3492
  }
3702
-
3703
- // begin the text object
3704
3493
  this.addContent('BT');
3705
-
3706
- // text position
3707
3494
  this.addContent(`1 0 0 1 ${number(x)} ${number(y)} Tm`);
3708
-
3709
- // font and font size
3710
3495
  this.addContent(`/${this._font.id} ${number(this._fontSize)} Tf`);
3711
-
3712
- // rendering mode
3713
3496
  const mode = options.fill && options.stroke ? 2 : options.stroke ? 1 : 0;
3714
3497
  if (mode) {
3715
3498
  this.addContent(`${mode} Tr`);
3716
3499
  }
3717
-
3718
- // Character spacing
3719
3500
  if (characterSpacing) {
3720
3501
  this.addContent(`${number(characterSpacing)} Tc`);
3721
3502
  }
3722
-
3723
- // Horizontal scaling
3724
3503
  if (horizontalScaling !== 100) {
3725
3504
  this.addContent(`${horizontalScaling} Tz`);
3726
3505
  }
3727
-
3728
- // Add the actual text
3729
- // If we have a word spacing value, we need to encode each word separately
3730
- // since the normal Tw operator only works on character code 32, which isn't
3731
- // used for embedded fonts.
3732
3506
  if (wordSpacing) {
3733
3507
  words = text.trim().split(/\s+/);
3734
3508
  wordSpacing += this.widthOfString(' ') + characterSpacing;
@@ -3739,9 +3513,6 @@ var TextMixin = {
3739
3513
  const [encodedWord, positionsWord] = this._font.encode(word, options.features);
3740
3514
  encoded = encoded.concat(encodedWord);
3741
3515
  positions = positions.concat(positionsWord);
3742
-
3743
- // add the word spacing to the end of the word
3744
- // clone object because of cache
3745
3516
  const space = {};
3746
3517
  const object = positions[positions.length - 1];
3747
3518
  for (let key in object) {
@@ -3758,60 +3529,42 @@ var TextMixin = {
3758
3529
  const commands = [];
3759
3530
  let last = 0;
3760
3531
  let hadOffset = false;
3761
-
3762
- // Adds a segment of text to the TJ command buffer
3763
3532
  const addSegment = cur => {
3764
3533
  if (last < cur) {
3765
3534
  const hex = encoded.slice(last, cur).join('');
3766
3535
  const advance = positions[cur - 1].xAdvance - positions[cur - 1].advanceWidth;
3767
3536
  commands.push(`<${hex}> ${number(-advance)}`);
3768
3537
  }
3769
- return last = cur;
3538
+ last = cur;
3770
3539
  };
3771
-
3772
- // Flushes the current TJ commands to the output stream
3773
3540
  const flush = i => {
3774
3541
  addSegment(i);
3775
3542
  if (commands.length > 0) {
3776
3543
  this.addContent(`[${commands.join(' ')}] TJ`);
3777
- return commands.length = 0;
3544
+ commands.length = 0;
3778
3545
  }
3779
3546
  };
3780
3547
  for (i = 0; i < positions.length; i++) {
3781
- // If we have an x or y offset, we have to break out of the current TJ command
3782
- // so we can move the text position.
3783
3548
  const pos = positions[i];
3784
3549
  if (pos.xOffset || pos.yOffset) {
3785
- // Flush the current buffer
3786
3550
  flush(i);
3787
-
3788
- // Move the text position and flush just the current character
3789
3551
  this.addContent(`1 0 0 1 ${number(x + pos.xOffset * scale)} ${number(y + pos.yOffset * scale)} Tm`);
3790
3552
  flush(i + 1);
3791
3553
  hadOffset = true;
3792
3554
  } else {
3793
- // If the last character had an offset, reset the text position
3794
3555
  if (hadOffset) {
3795
3556
  this.addContent(`1 0 0 1 ${number(x)} ${number(y)} Tm`);
3796
3557
  hadOffset = false;
3797
3558
  }
3798
-
3799
- // Group segments that don't have any advance adjustments
3800
3559
  if (pos.xAdvance - pos.advanceWidth !== 0) {
3801
3560
  addSegment(i + 1);
3802
3561
  }
3803
3562
  }
3804
3563
  x += pos.xAdvance * scale;
3805
3564
  }
3806
-
3807
- // Flush any remaining commands
3808
3565
  flush(i);
3809
-
3810
- // end the text object
3811
3566
  this.addContent('ET');
3812
-
3813
- // restore flipped coordinate system
3814
- return this.restore();
3567
+ this.restore();
3815
3568
  }
3816
3569
  };
3817
3570
 
@@ -3829,8 +3582,6 @@ class JPEG {
3829
3582
  if (this.data.readUInt16BE(0) !== 0xffd8) {
3830
3583
  throw 'SOI not found in JPEG';
3831
3584
  }
3832
-
3833
- // Parse the EXIF orientation
3834
3585
  this.orientation = exif.fromBuffer(this.data).Orientation || 1;
3835
3586
  let pos = 2;
3836
3587
  while (pos < this.data.length) {
@@ -3867,16 +3618,10 @@ class JPEG {
3867
3618
  ColorSpace: this.colorSpace,
3868
3619
  Filter: 'DCTDecode'
3869
3620
  });
3870
-
3871
- // add extra decode params for CMYK images. By swapping the
3872
- // min and max values from the default, we invert the colors. See
3873
- // section 4.8.4 of the spec.
3874
3621
  if (this.colorSpace === 'DeviceCMYK') {
3875
3622
  this.obj.data['Decode'] = [1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0];
3876
3623
  }
3877
3624
  this.obj.end(this.data);
3878
-
3879
- // free memory
3880
3625
  return this.data = null;
3881
3626
  }
3882
3627
  }
@@ -3919,24 +3664,14 @@ class PNGImage {
3919
3664
  if (this.image.palette.length === 0) {
3920
3665
  this.obj.data['ColorSpace'] = this.image.colorSpace;
3921
3666
  } else {
3922
- // embed the color palette in the PDF as an object stream
3923
3667
  const palette = this.document.ref();
3924
3668
  palette.end(Buffer.from(this.image.palette));
3925
-
3926
- // build the color space array for the image
3927
3669
  this.obj.data['ColorSpace'] = ['Indexed', 'DeviceRGB', this.image.palette.length / 3 - 1, palette];
3928
3670
  }
3929
-
3930
- // For PNG color types 0, 2 and 3, the transparency data is stored in
3931
- // a dedicated PNG chunk.
3932
3671
  if (this.image.transparency.grayscale != null) {
3933
- // Use Color Key Masking (spec section 4.8.5)
3934
- // An array with N elements, where N is two times the number of color components.
3935
3672
  const val = this.image.transparency.grayscale;
3936
3673
  this.obj.data['Mask'] = [val, val];
3937
3674
  } else if (this.image.transparency.rgb) {
3938
- // Use Color Key Masking (spec section 4.8.5)
3939
- // An array with N elements, where N is two times the number of color components.
3940
3675
  const {
3941
3676
  rgb
3942
3677
  } = this.image.transparency;
@@ -3946,14 +3681,9 @@ class PNGImage {
3946
3681
  }
3947
3682
  this.obj.data['Mask'] = mask;
3948
3683
  } else if (this.image.transparency.indexed) {
3949
- // Create a transparency SMask for the image based on the data
3950
- // in the PLTE and tRNS sections. See below for details on SMasks.
3951
3684
  dataDecoded = true;
3952
3685
  return this.loadIndexedAlphaChannel();
3953
3686
  } else if (hasAlphaChannel) {
3954
- // For PNG color types 4 and 6, the transparency data is stored as a alpha
3955
- // channel mixed in with the main image data. Separate this data out into an
3956
- // SMask object and store it separately in the PDF.
3957
3687
  dataDecoded = true;
3958
3688
  return this.splitAlphaChannel();
3959
3689
  }
@@ -3977,11 +3707,7 @@ class PNGImage {
3977
3707
  sMask.end(this.alphaChannel);
3978
3708
  this.obj.data['SMask'] = sMask;
3979
3709
  }
3980
-
3981
- // add the actual image data
3982
3710
  this.obj.end(this.imgData);
3983
-
3984
- // free memory
3985
3711
  this.image = null;
3986
3712
  return this.imgData = null;
3987
3713
  }
@@ -3994,7 +3720,6 @@ class PNGImage {
3994
3720
  const alphaChannel = Buffer.alloc(pixelCount);
3995
3721
  let i = p = a = 0;
3996
3722
  const len = pixels.length;
3997
- // For 16bit images copy only most significant byte (MSB) - PNG data is always stored in network byte order (MSB first)
3998
3723
  const skipByteCount = this.image.bits === 16 ? 1 : 0;
3999
3724
  while (i < len) {
4000
3725
  for (let colorIndex = 0; colorIndex < colorCount; colorIndex++) {
@@ -4029,10 +3754,6 @@ class PNGImage {
4029
3754
  }
4030
3755
  }
4031
3756
 
4032
- /*
4033
- PDFImage - embeds images in PDF documents
4034
- By Devon Govett
4035
- */
4036
3757
  class PDFImage {
4037
3758
  static open(src, label) {
4038
3759
  let data;
@@ -4064,18 +3785,17 @@ class PDFImage {
4064
3785
  var ImagesMixin = {
4065
3786
  initImages() {
4066
3787
  this._imageRegistry = {};
4067
- return this._imageCount = 0;
3788
+ this._imageCount = 0;
4068
3789
  },
4069
3790
  image(src, x, y) {
4070
3791
  let options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
4071
- let bh, bp, bw, image, ip, left, left1, rotateAngle, originX, originY;
3792
+ let bh, bp, bw, image, ip, left, left1, originX, originY;
4072
3793
  if (typeof x === 'object') {
4073
3794
  options = x;
4074
3795
  x = null;
4075
3796
  }
4076
-
4077
- // Ignore orientation based on document options or image options
4078
3797
  const ignoreOrientation = options.ignoreOrientation || options.ignoreOrientation !== false && this.options.ignoreOrientation;
3798
+ const inDocumentFlow = typeof y !== 'number';
4079
3799
  x = (left = x != null ? x : options.x) != null ? left : this.x;
4080
3800
  y = (left1 = y != null ? y : options.y) != null ? left1 : this.y;
4081
3801
  if (typeof src === 'string') {
@@ -4098,8 +3818,6 @@ var ImagesMixin = {
4098
3818
  width,
4099
3819
  height
4100
3820
  } = image;
4101
-
4102
- // If EXIF orientation calls for it, swap width and height
4103
3821
  if (!ignoreOrientation && image.orientation > 4) {
4104
3822
  [width, height] = [height, width];
4105
3823
  }
@@ -4151,80 +3869,70 @@ var ImagesMixin = {
4151
3869
  y = y + bh - h;
4152
3870
  }
4153
3871
  }
3872
+ let rotateAngle = 0;
3873
+ let xTransform = x;
3874
+ let yTransform = y;
3875
+ let hTransform = h;
3876
+ let wTransform = w;
4154
3877
  if (!ignoreOrientation) {
4155
3878
  switch (image.orientation) {
4156
- // No orientation (need to flip image, though, because of the default transform matrix on the document)
4157
3879
  default:
4158
3880
  case 1:
4159
- h = -h;
4160
- y -= h;
4161
- rotateAngle = 0;
3881
+ hTransform = -h;
3882
+ yTransform += h;
4162
3883
  break;
4163
- // Flip Horizontal
4164
3884
  case 2:
4165
- w = -w;
4166
- h = -h;
4167
- x -= w;
4168
- y -= h;
4169
- rotateAngle = 0;
3885
+ wTransform = -w;
3886
+ hTransform = -h;
3887
+ xTransform += w;
3888
+ yTransform += h;
4170
3889
  break;
4171
- // Rotate 180 degrees
4172
3890
  case 3:
4173
3891
  originX = x;
4174
3892
  originY = y;
4175
- h = -h;
4176
- x -= w;
3893
+ hTransform = -h;
3894
+ xTransform -= w;
4177
3895
  rotateAngle = 180;
4178
3896
  break;
4179
- // Flip vertical
4180
3897
  case 4:
4181
- // Do nothing, image will be flipped
4182
-
4183
3898
  break;
4184
- // Flip horizontally and rotate 270 degrees CW
4185
3899
  case 5:
4186
3900
  originX = x;
4187
3901
  originY = y;
4188
- [w, h] = [h, w];
4189
- y -= h;
3902
+ wTransform = h;
3903
+ hTransform = w;
3904
+ yTransform -= hTransform;
4190
3905
  rotateAngle = 90;
4191
3906
  break;
4192
- // Rotate 90 degrees CW
4193
3907
  case 6:
4194
3908
  originX = x;
4195
3909
  originY = y;
4196
- [w, h] = [h, w];
4197
- h = -h;
3910
+ wTransform = h;
3911
+ hTransform = -w;
4198
3912
  rotateAngle = 90;
4199
3913
  break;
4200
- // Flip horizontally and rotate 90 degrees CW
4201
3914
  case 7:
4202
3915
  originX = x;
4203
3916
  originY = y;
4204
- [w, h] = [h, w];
4205
- h = -h;
4206
- w = -w;
4207
- x -= w;
3917
+ hTransform = -w;
3918
+ wTransform = -h;
3919
+ xTransform += h;
4208
3920
  rotateAngle = 90;
4209
3921
  break;
4210
- // Rotate 270 degrees CW
4211
3922
  case 8:
4212
3923
  originX = x;
4213
3924
  originY = y;
4214
- [w, h] = [h, w];
4215
- h = -h;
4216
- x -= w;
4217
- y -= h;
3925
+ wTransform = h;
3926
+ hTransform = -w;
3927
+ xTransform -= h;
3928
+ yTransform += w;
4218
3929
  rotateAngle = -90;
4219
3930
  break;
4220
3931
  }
4221
3932
  } else {
4222
- h = -h;
4223
- y -= h;
4224
- rotateAngle = 0;
3933
+ hTransform = -h;
3934
+ yTransform += h;
4225
3935
  }
4226
-
4227
- // create link annotations if the link option is given
4228
3936
  if (options.link != null) {
4229
3937
  this.link(x, y, w, h, options.link);
4230
3938
  }
@@ -4234,9 +3942,7 @@ var ImagesMixin = {
4234
3942
  if (options.destination != null) {
4235
3943
  this.addNamedDestination(options.destination, 'XYZ', x, y, null);
4236
3944
  }
4237
-
4238
- // Set the current y position to below the image if it is in the document flow
4239
- if (this.y === y) {
3945
+ if (inDocumentFlow) {
4240
3946
  this.y += h;
4241
3947
  }
4242
3948
  this.save();
@@ -4245,7 +3951,7 @@ var ImagesMixin = {
4245
3951
  origin: [originX, originY]
4246
3952
  });
4247
3953
  }
4248
- this.transform(w, 0, 0, h, x, y);
3954
+ this.transform(wTransform, 0, 0, hTransform, xTransform, yTransform);
4249
3955
  this.addContent(`/${image.label} Do`);
4250
3956
  this.restore();
4251
3957
  return this;
@@ -4271,19 +3977,17 @@ var AnnotationsMixin = {
4271
3977
  options.Rect = this._convertRect(x, y, w, h);
4272
3978
  options.Border = [0, 0, 0];
4273
3979
  if (options.Subtype === 'Link' && typeof options.F === 'undefined') {
4274
- options.F = 1 << 2; // Print Annotation Flag
3980
+ options.F = 1 << 2;
4275
3981
  }
4276
3982
  if (options.Subtype !== 'Link') {
4277
3983
  if (options.C == null) {
4278
3984
  options.C = this._normalizeColor(options.color || [0, 0, 0]);
4279
3985
  }
4280
- } // convert colors
3986
+ }
4281
3987
  delete options.color;
4282
3988
  if (typeof options.Dest === 'string') {
4283
3989
  options.Dest = new String(options.Dest);
4284
3990
  }
4285
-
4286
- // Capitalize keys
4287
3991
  for (let key in options) {
4288
3992
  const val = options[key];
4289
3993
  options[key[0].toUpperCase() + key.slice(1)] = val;
@@ -4319,7 +4023,6 @@ var AnnotationsMixin = {
4319
4023
  let options = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : {};
4320
4024
  options.Subtype = 'Link';
4321
4025
  if (typeof url === 'number') {
4322
- // Link to a page in the document (the page must already exist)
4323
4026
  const pages = this._root.data.Pages.data;
4324
4027
  if (url >= 0 && url < pages.Kids.length) {
4325
4028
  options.A = this.ref({
@@ -4331,7 +4034,6 @@ var AnnotationsMixin = {
4331
4034
  throw new Error(`The document has no page ${url}`);
4332
4035
  }
4333
4036
  } else {
4334
- // Link to an external url
4335
4037
  options.A = this.ref({
4336
4038
  S: 'URI',
4337
4039
  URI: new String(url)
@@ -4394,14 +4096,11 @@ var AnnotationsMixin = {
4394
4096
  fileAnnotation(x, y, w, h) {
4395
4097
  let file = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {};
4396
4098
  let options = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : {};
4397
- // create hidden file
4398
4099
  const filespec = this.file(file.src, Object.assign({
4399
4100
  hidden: true
4400
4101
  }, file));
4401
4102
  options.Subtype = 'FileAttachment';
4402
4103
  options.FS = filespec;
4403
-
4404
- // add description from filespec unless description (Contents) has already been set
4405
4104
  if (options.Contents) {
4406
4105
  options.Contents = new String(options.Contents);
4407
4106
  } else if (filespec.data.Desc) {
@@ -4410,14 +4109,9 @@ var AnnotationsMixin = {
4410
4109
  return this.annotate(x, y, w, h, options);
4411
4110
  },
4412
4111
  _convertRect(x1, y1, w, h) {
4413
- // flip y1 and y2
4414
4112
  let y2 = y1;
4415
4113
  y1 += h;
4416
-
4417
- // make x2
4418
4114
  let x2 = x1 + w;
4419
-
4420
- // apply current transformation matrix to points
4421
4115
  const [m0, m1, m2, m3, m4, m5] = this._ctm;
4422
4116
  x1 = m0 * x1 + m2 * y1 + m4;
4423
4117
  y1 = m1 * x1 + m3 * y1 + m5;
@@ -4481,7 +4175,7 @@ class PDFOutline {
4481
4175
 
4482
4176
  var OutlineMixin = {
4483
4177
  initOutline() {
4484
- return this.outline = new PDFOutline(this, null, null, null);
4178
+ this.outline = new PDFOutline(this, null, null, null);
4485
4179
  },
4486
4180
  endOutline() {
4487
4181
  this.outline.endOutline();
@@ -4492,11 +4186,6 @@ var OutlineMixin = {
4492
4186
  }
4493
4187
  };
4494
4188
 
4495
- /*
4496
- PDFStructureContent - a reference to a marked structure content
4497
- By Ben Schmidt
4498
- */
4499
-
4500
4189
  class PDFStructureContent {
4501
4190
  constructor(pageRef, mcid) {
4502
4191
  this.refs = [{
@@ -4509,10 +4198,6 @@ class PDFStructureContent {
4509
4198
  }
4510
4199
  }
4511
4200
 
4512
- /*
4513
- PDFStructureElement - represents an element in the PDF logical structure tree
4514
- By Ben Schmidt
4515
- */
4516
4201
  class PDFStructureElement {
4517
4202
  constructor(document, type) {
4518
4203
  let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
@@ -4522,7 +4207,6 @@ class PDFStructureElement {
4522
4207
  this._ended = false;
4523
4208
  this._flushed = false;
4524
4209
  this.dictionary = document.ref({
4525
- // Type: "StructElem",
4526
4210
  S: type
4527
4211
  });
4528
4212
  const data = this.dictionary.data;
@@ -4571,7 +4255,6 @@ class PDFStructureElement {
4571
4255
  this._addContentToParentTree(child);
4572
4256
  }
4573
4257
  if (typeof child === 'function' && this._attached) {
4574
- // _contentForClosure() adds the content to the parent tree
4575
4258
  child = this._contentForClosure(child);
4576
4259
  }
4577
4260
  this._children.push(child);
@@ -4648,10 +4331,6 @@ class PDFStructureElement {
4648
4331
  this.dictionary.data.K = [];
4649
4332
  this._children.forEach(child => this._flushChild(child));
4650
4333
  this.dictionary.end();
4651
-
4652
- // free memory used by children; the dictionary itself may still be
4653
- // referenced by a parent structure element or root, but we can
4654
- // at least trim the tree here
4655
4334
  this._children = [];
4656
4335
  this.dictionary.data.K = null;
4657
4336
  this._flushed = true;
@@ -4673,7 +4352,7 @@ class PDFStructureElement {
4673
4352
  this.dictionary.data.K.push(mcid);
4674
4353
  } else {
4675
4354
  this.dictionary.data.K.push({
4676
- Type: "MCR",
4355
+ Type: 'MCR',
4677
4356
  Pg: pageRef,
4678
4357
  MCID: mcid
4679
4358
  });
@@ -4683,25 +4362,18 @@ class PDFStructureElement {
4683
4362
  }
4684
4363
  }
4685
4364
 
4686
- /*
4687
- PDFNumberTree - represents a number tree object
4688
- */
4689
4365
  class PDFNumberTree extends PDFTree {
4690
4366
  _compareKeys(a, b) {
4691
4367
  return parseInt(a) - parseInt(b);
4692
4368
  }
4693
4369
  _keysName() {
4694
- return "Nums";
4370
+ return 'Nums';
4695
4371
  }
4696
4372
  _dataForKey(k) {
4697
4373
  return parseInt(k);
4698
4374
  }
4699
4375
  }
4700
4376
 
4701
- /*
4702
- Markings mixin - support marked content sequences in content streams
4703
- By Ben Schmidt
4704
- */
4705
4377
  var MarkingsMixin = {
4706
4378
  initMarkings(options) {
4707
4379
  this.structChildren = [];
@@ -4841,7 +4513,6 @@ var MarkingsMixin = {
4841
4513
  return this.getStructTreeRoot().data.ParentTree;
4842
4514
  },
4843
4515
  createStructParentTreeNextKey() {
4844
- // initialise the MarkInfo dictionary
4845
4516
  this.getMarkInfoDictionary();
4846
4517
  const structTreeRoot = this.getStructTreeRoot();
4847
4518
  const key = structTreeRoot.data.ParentTreeNextKey++;
@@ -4905,10 +4576,6 @@ const FORMAT_DEFAULT = {
4905
4576
  }
4906
4577
  };
4907
4578
  var AcroFormMixin = {
4908
- /**
4909
- * Must call if adding AcroForms to a document. Must also call font() before
4910
- * this method to set the default font.
4911
- */
4912
4579
  initForm() {
4913
4580
  if (!this._font) {
4914
4581
  throw new Error('Must set a font before calling initForm method');
@@ -4931,9 +4598,6 @@ var AcroFormMixin = {
4931
4598
  this._root.data.AcroForm = AcroForm;
4932
4599
  return this;
4933
4600
  },
4934
- /**
4935
- * Called automatically by document.js
4936
- */
4937
4601
  endAcroForm() {
4938
4602
  if (this._root.data.AcroForm) {
4939
4603
  if (!Object.keys(this._acroform.fonts).length && !this._acroform.defaultFont) {
@@ -4959,13 +4623,6 @@ var AcroFormMixin = {
4959
4623
  }
4960
4624
  return this;
4961
4625
  },
4962
- /**
4963
- * Creates and adds a form field to the document. Form fields are intermediate
4964
- * nodes in a PDF form that are used to specify form name heirarchy and form
4965
- * value defaults.
4966
- * @param {string} name - field name (T attribute in field dictionary)
4967
- * @param {object} options - other attributes to include in field dictionary
4968
- */
4969
4626
  formField(name) {
4970
4627
  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
4971
4628
  let fieldDict = this._fieldDict(name, null, options);
@@ -4973,26 +4630,13 @@ var AcroFormMixin = {
4973
4630
  this._addToParent(fieldRef);
4974
4631
  return fieldRef;
4975
4632
  },
4976
- /**
4977
- * Creates and adds a Form Annotation to the document. Form annotations are
4978
- * called Widget annotations internally within a PDF file.
4979
- * @param {string} name - form field name (T attribute of widget annotation
4980
- * dictionary)
4981
- * @param {number} x
4982
- * @param {number} y
4983
- * @param {number} w
4984
- * @param {number} h
4985
- * @param {object} options
4986
- */
4987
4633
  formAnnotation(name, type, x, y, w, h) {
4988
4634
  let options = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : {};
4989
4635
  let fieldDict = this._fieldDict(name, type, options);
4990
4636
  fieldDict.Subtype = 'Widget';
4991
4637
  if (fieldDict.F === undefined) {
4992
- fieldDict.F = 4; // print the annotation
4638
+ fieldDict.F = 4;
4993
4639
  }
4994
-
4995
- // Add Field annot to page, and get it's ref
4996
4640
  this.annotate(x, y, w, h, fieldDict);
4997
4641
  let annotRef = this.page.annotations[this.page.annotations.length - 1];
4998
4642
  return this._addToParent(annotRef);
@@ -5160,23 +4804,18 @@ var AcroFormMixin = {
5160
4804
  delete options.align;
5161
4805
  }
5162
4806
  if (result !== 0) {
5163
- options.Q = result; // default
4807
+ options.Q = result;
5164
4808
  }
5165
4809
  return options;
5166
4810
  },
5167
4811
  _resolveFont(options) {
5168
- // add current font to document-level AcroForm dict if necessary
5169
4812
  if (this._acroform.fonts[this._font.id] == null) {
5170
4813
  this._acroform.fonts[this._font.id] = this._font.ref();
5171
4814
  }
5172
-
5173
- // add current font to field's resource dict (RD) if not the default acroform font
5174
4815
  if (this._acroform.defaultFont !== this._font.name) {
5175
4816
  options.DR = {
5176
4817
  Font: {}
5177
4818
  };
5178
-
5179
- // Get the fontSize option. If not set use auto sizing
5180
4819
  const fontSize = options.fontSize || 0;
5181
4820
  options.DR.Font[this._font.id] = this._font.ref();
5182
4821
  options.DA = new String(`/${this._font.id} ${fontSize} Tf 0 g`);
@@ -5228,19 +4867,6 @@ var AcroFormMixin = {
5228
4867
  };
5229
4868
 
5230
4869
  var AttachmentsMixin = {
5231
- /**
5232
- * Embed contents of `src` in PDF
5233
- * @param {Buffer | ArrayBuffer | string} src input Buffer, ArrayBuffer, base64 encoded string or path to file
5234
- * @param {object} options
5235
- * * options.name: filename to be shown in PDF, will use `src` if none set
5236
- * * options.type: filetype to be shown in PDF
5237
- * * options.description: description to be shown in PDF
5238
- * * options.hidden: if true, do not add attachment to EmbeddedFiles dictionary. Useful for file attachment annotations
5239
- * * options.creationDate: override creation date
5240
- * * options.modifiedDate: override modified date
5241
- * * options.relationship: Relationship between the PDF document and its attached file. Can be 'Alternative', 'Data', 'Source', 'Supplement' or 'Unspecified'.
5242
- * @returns filespec reference
5243
- */
5244
4870
  file(src) {
5245
4871
  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
5246
4872
  options.name = options.name || src;
@@ -5269,8 +4895,6 @@ var AttachmentsMixin = {
5269
4895
  if (!data) {
5270
4896
  throw new Error(`Could not read contents of file at filepath ${src}`);
5271
4897
  }
5272
-
5273
- // update CreationDate and ModDate
5274
4898
  const {
5275
4899
  birthtime,
5276
4900
  ctime
@@ -5279,26 +4903,18 @@ var AttachmentsMixin = {
5279
4903
  refBody.Params.ModDate = ctime;
5280
4904
  }
5281
4905
  }
5282
-
5283
- // override creation date and modified date
5284
4906
  if (options.creationDate instanceof Date) {
5285
4907
  refBody.Params.CreationDate = options.creationDate;
5286
4908
  }
5287
4909
  if (options.modifiedDate instanceof Date) {
5288
4910
  refBody.Params.ModDate = options.modifiedDate;
5289
4911
  }
5290
- // add optional subtype
5291
4912
  if (options.type) {
5292
4913
  refBody.Subtype = options.type.replace('/', '#2F');
5293
4914
  }
5294
-
5295
- // add checksum and size information
5296
4915
  const checksum = CryptoJS.MD5(CryptoJS.lib.WordArray.create(new Uint8Array(data)));
5297
4916
  refBody.Params.CheckSum = new String(checksum);
5298
4917
  refBody.Params.Size = data.byteLength;
5299
-
5300
- // save some space when embedding the same file again
5301
- // if a file with the same name and metadata exists, reuse its reference
5302
4918
  let ref;
5303
4919
  if (!this._fileRegistry) this._fileRegistry = {};
5304
4920
  let file = this._fileRegistry[options.name];
@@ -5312,7 +4928,6 @@ var AttachmentsMixin = {
5312
4928
  ref
5313
4929
  };
5314
4930
  }
5315
- // add filespec for embedded file
5316
4931
  const fileSpecBody = {
5317
4932
  Type: 'Filespec',
5318
4933
  AFRelationship: options.relationship,
@@ -5330,8 +4945,6 @@ var AttachmentsMixin = {
5330
4945
  if (!options.hidden) {
5331
4946
  this.addNamedEmbeddedFile(options.name, filespec);
5332
4947
  }
5333
-
5334
- // Add file to the catalogue to be PDF/A3 compliant
5335
4948
  if (this._root.data.AF) {
5336
4949
  this._root.data.AF.push(filespec);
5337
4950
  } else {
@@ -5340,8 +4953,6 @@ var AttachmentsMixin = {
5340
4953
  return filespec;
5341
4954
  }
5342
4955
  };
5343
-
5344
- /** check two embedded file metadata objects for equality */
5345
4956
  function isEqual(a, b) {
5346
4957
  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());
5347
4958
  }
@@ -5352,7 +4963,6 @@ var PDFA = {
5352
4963
  this.subset_conformance = pSubset.charAt(pSubset.length - 1).toUpperCase();
5353
4964
  this.subset = parseInt(pSubset.charAt(pSubset.length - 2));
5354
4965
  } else {
5355
- // Default to Basic conformance when user doesn't specify
5356
4966
  this.subset_conformance = 'B';
5357
4967
  this.subset = parseInt(pSubset.charAt(pSubset.length - 1));
5358
4968
  }
@@ -5437,6 +5047,656 @@ var SubsetMixin = {
5437
5047
  }
5438
5048
  };
5439
5049
 
5050
+ const ROW_FIELDS = ['height', 'minHeight', 'maxHeight'];
5051
+ const COLUMN_FIELDS = ['width', 'minWidth', 'maxWidth'];
5052
+ function memoize(fn, maxSize) {
5053
+ const cache = new Map();
5054
+ return function () {
5055
+ const key = arguments.length <= 0 ? undefined : arguments[0];
5056
+ if (!cache.has(key)) {
5057
+ cache.set(key, fn(...arguments));
5058
+ if (cache.size > maxSize) cache.delete(cache.keys().next());
5059
+ }
5060
+ return cache.get(key);
5061
+ };
5062
+ }
5063
+ function isObject(item) {
5064
+ return item && typeof item === 'object' && !Array.isArray(item);
5065
+ }
5066
+ function deepMerge(target) {
5067
+ if (!isObject(target)) return target;
5068
+ target = deepClone(target);
5069
+ for (var _len = arguments.length, sources = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
5070
+ sources[_key - 1] = arguments[_key];
5071
+ }
5072
+ for (const source of sources) {
5073
+ if (isObject(source)) {
5074
+ for (const key in source) {
5075
+ if (isObject(source[key])) {
5076
+ if (!(key in target)) target[key] = {};
5077
+ target[key] = deepMerge(target[key], source[key]);
5078
+ } else if (source[key] !== undefined) {
5079
+ target[key] = deepClone(source[key]);
5080
+ }
5081
+ }
5082
+ }
5083
+ }
5084
+ return target;
5085
+ }
5086
+ function deepClone(obj) {
5087
+ let result = obj;
5088
+ if (typeof obj == 'object') {
5089
+ result = Array.isArray(obj) ? [] : {};
5090
+ for (const key in obj) result[key] = deepClone(obj[key]);
5091
+ }
5092
+ return result;
5093
+ }
5094
+
5095
+ function normalizedDefaultStyle(defaultStyleInternal) {
5096
+ let defaultStyle = defaultStyleInternal;
5097
+ if (typeof defaultStyle !== 'object') defaultStyle = {
5098
+ text: defaultStyle
5099
+ };
5100
+ const defaultRowStyle = Object.fromEntries(Object.entries(defaultStyle).filter(_ref => {
5101
+ let [k] = _ref;
5102
+ return ROW_FIELDS.includes(k);
5103
+ }));
5104
+ const defaultColStyle = Object.fromEntries(Object.entries(defaultStyle).filter(_ref2 => {
5105
+ let [k] = _ref2;
5106
+ return COLUMN_FIELDS.includes(k);
5107
+ }));
5108
+ defaultStyle.padding = normalizeSides(defaultStyle.padding);
5109
+ defaultStyle.border = normalizeSides(defaultStyle.border);
5110
+ defaultStyle.borderColor = normalizeSides(defaultStyle.borderColor);
5111
+ defaultStyle.align = normalizeAlignment(defaultStyle.align);
5112
+ return {
5113
+ defaultStyle,
5114
+ defaultRowStyle,
5115
+ defaultColStyle
5116
+ };
5117
+ }
5118
+ function normalizedRowStyle(defaultRowStyle, rowStyleInternal, i) {
5119
+ let rowStyle = rowStyleInternal(i);
5120
+ if (rowStyle == null || typeof rowStyle !== 'object') {
5121
+ rowStyle = {
5122
+ height: rowStyle
5123
+ };
5124
+ }
5125
+ rowStyle.padding = normalizeSides(rowStyle.padding);
5126
+ rowStyle.border = normalizeSides(rowStyle.border);
5127
+ rowStyle.borderColor = normalizeSides(rowStyle.borderColor);
5128
+ rowStyle.align = normalizeAlignment(rowStyle.align);
5129
+ rowStyle = deepMerge(defaultRowStyle, rowStyle);
5130
+ const document = this.document;
5131
+ const page = document.page;
5132
+ const contentHeight = page.contentHeight;
5133
+ if (rowStyle.height == null || rowStyle.height === 'auto') {
5134
+ rowStyle.height = 'auto';
5135
+ } else {
5136
+ rowStyle.height = document.sizeToPoint(rowStyle.height, 0, page, contentHeight);
5137
+ }
5138
+ rowStyle.minHeight = document.sizeToPoint(rowStyle.minHeight, 0, page, contentHeight);
5139
+ rowStyle.maxHeight = document.sizeToPoint(rowStyle.maxHeight, 0, page, contentHeight);
5140
+ return rowStyle;
5141
+ }
5142
+ function normalizedColumnStyle(defaultColStyle, colStyleInternal, i) {
5143
+ let colStyle = colStyleInternal(i);
5144
+ if (colStyle == null || typeof colStyle !== 'object') {
5145
+ colStyle = {
5146
+ width: colStyle
5147
+ };
5148
+ }
5149
+ colStyle.padding = normalizeSides(colStyle.padding);
5150
+ colStyle.border = normalizeSides(colStyle.border);
5151
+ colStyle.borderColor = normalizeSides(colStyle.borderColor);
5152
+ colStyle.align = normalizeAlignment(colStyle.align);
5153
+ colStyle = deepMerge(defaultColStyle, colStyle);
5154
+ if (colStyle.width == null || colStyle.width === '*') {
5155
+ colStyle.width = '*';
5156
+ } else {
5157
+ colStyle.width = this.document.sizeToPoint(colStyle.width, 0, this.document.page, this._maxWidth);
5158
+ }
5159
+ colStyle.minWidth = this.document.sizeToPoint(colStyle.minWidth, 0, this.document.page, this._maxWidth);
5160
+ colStyle.maxWidth = this.document.sizeToPoint(colStyle.maxWidth, 0, this.document.page, this._maxWidth);
5161
+ return colStyle;
5162
+ }
5163
+ function normalizeAlignment(align) {
5164
+ return align == null || typeof align === 'string' ? {
5165
+ x: align,
5166
+ y: align
5167
+ } : align;
5168
+ }
5169
+
5170
+ function normalizeTable() {
5171
+ const doc = this.document;
5172
+ const opts = this.opts;
5173
+ let index = doc._tableIndex++;
5174
+ this._id = new String(opts.id ?? `table-${index}`);
5175
+ this._position = {
5176
+ x: doc.sizeToPoint(opts.position?.x, doc.x),
5177
+ y: doc.sizeToPoint(opts.position?.y, doc.y)
5178
+ };
5179
+ this._maxWidth = doc.sizeToPoint(opts.maxWidth, doc.page.width - doc.page.margins.right - this._position.x);
5180
+ const {
5181
+ defaultStyle,
5182
+ defaultColStyle,
5183
+ defaultRowStyle
5184
+ } = normalizedDefaultStyle(opts.defaultStyle);
5185
+ this._defaultStyle = defaultStyle;
5186
+ let colStyle;
5187
+ if (opts.columnStyles) {
5188
+ if (Array.isArray(opts.columnStyles)) {
5189
+ colStyle = i => opts.columnStyles[i];
5190
+ } else if (typeof opts.columnStyles === 'function') {
5191
+ colStyle = memoize(i => opts.columnStyles(i), Infinity);
5192
+ } else if (typeof opts.columnStyles === 'object') {
5193
+ colStyle = () => opts.columnStyles;
5194
+ }
5195
+ }
5196
+ if (!colStyle) colStyle = () => ({});
5197
+ this._colStyle = normalizedColumnStyle.bind(this, defaultColStyle, colStyle);
5198
+ let rowStyle;
5199
+ if (opts.rowStyles) {
5200
+ if (Array.isArray(opts.rowStyles)) {
5201
+ rowStyle = i => opts.rowStyles[i];
5202
+ } else if (typeof opts.rowStyles === 'function') {
5203
+ rowStyle = memoize(i => opts.rowStyles(i), 10);
5204
+ } else if (typeof opts.rowStyles === 'object') {
5205
+ rowStyle = () => opts.rowStyles;
5206
+ }
5207
+ }
5208
+ if (!rowStyle) rowStyle = () => ({});
5209
+ this._rowStyle = normalizedRowStyle.bind(this, defaultRowStyle, rowStyle);
5210
+ }
5211
+ function normalizeText(text) {
5212
+ if (text != null) text = `${text}`;
5213
+ return text;
5214
+ }
5215
+ function normalizeCell(cell, rowIndex, colIndex) {
5216
+ const colStyle = this._colStyle(colIndex);
5217
+ let rowStyle = this._rowStyle(rowIndex);
5218
+ const font = deepMerge({}, colStyle.font, rowStyle.font, cell.font);
5219
+ const customFont = Object.values(font).filter(v => v != null).length > 0;
5220
+ const doc = this.document;
5221
+ const rollbackFont = doc._fontSource;
5222
+ const rollbackFontSize = doc._fontSize;
5223
+ const rollbackFontFamily = doc._fontFamily;
5224
+ if (customFont) {
5225
+ if (font.src) doc.font(font.src, font.family);
5226
+ if (font.size) doc.fontSize(font.size);
5227
+ rowStyle = this._rowStyle(rowIndex);
5228
+ }
5229
+ cell.padding = normalizeSides(cell.padding);
5230
+ cell.border = normalizeSides(cell.border);
5231
+ cell.borderColor = normalizeSides(cell.borderColor);
5232
+ const config = deepMerge(this._defaultStyle, colStyle, rowStyle, cell);
5233
+ config.rowIndex = rowIndex;
5234
+ config.colIndex = colIndex;
5235
+ config.font = font ?? {};
5236
+ config.customFont = customFont;
5237
+ config.text = normalizeText(config.text);
5238
+ config.rowSpan = config.rowSpan ?? 1;
5239
+ config.colSpan = config.colSpan ?? 1;
5240
+ config.padding = normalizeSides(config.padding, '0.25em', x => doc.sizeToPoint(x, '0.25em'));
5241
+ config.border = normalizeSides(config.border, 1, x => doc.sizeToPoint(x, 1));
5242
+ config.borderColor = normalizeSides(config.borderColor, 'black', x => x ?? 'black');
5243
+ config.align = normalizeAlignment(config.align);
5244
+ config.align.x = config.align.x ?? 'left';
5245
+ config.align.y = config.align.y ?? 'top';
5246
+ config.textStroke = doc.sizeToPoint(config.textStroke, 0);
5247
+ config.textStrokeColor = config.textStrokeColor ?? 'black';
5248
+ config.textColor = config.textColor ?? 'black';
5249
+ config.textOptions = config.textOptions ?? {};
5250
+ config.id = new String(config.id ?? `${this._id}-${rowIndex}-${colIndex}`);
5251
+ config.type = config.type?.toUpperCase() === 'TH' ? 'TH' : 'TD';
5252
+ if (config.scope) {
5253
+ config.scope = config.scope.toLowerCase();
5254
+ if (config.scope === 'row') config.scope = 'Row';else if (config.scope === 'both') config.scope = 'Both';else if (config.scope === 'column') config.scope = 'Column';
5255
+ }
5256
+ if (typeof this.opts.debug === 'boolean') config.debug = this.opts.debug;
5257
+ if (customFont) doc.font(rollbackFont, rollbackFontFamily, rollbackFontSize);
5258
+ return config;
5259
+ }
5260
+ function normalizeRow(row, rowIndex) {
5261
+ if (!this._cellClaim) this._cellClaim = new Set();
5262
+ let colIndex = 0;
5263
+ return row.map(cell => {
5264
+ if (cell == null || typeof cell !== 'object') cell = {
5265
+ text: cell
5266
+ };
5267
+ while (this._cellClaim.has(`${rowIndex},${colIndex}`)) {
5268
+ colIndex++;
5269
+ }
5270
+ cell = normalizeCell.call(this, cell, rowIndex, colIndex);
5271
+ for (let i = 0; i < cell.rowSpan; i++) {
5272
+ for (let j = 0; j < cell.colSpan; j++) {
5273
+ this._cellClaim.add(`${rowIndex + i},${colIndex + j}`);
5274
+ }
5275
+ }
5276
+ colIndex += cell.colSpan;
5277
+ return cell;
5278
+ });
5279
+ }
5280
+
5281
+ function ensure(row) {
5282
+ this._columnWidths = [];
5283
+ ensureColumnWidths.call(this, row.reduce((a, cell) => a + cell.colSpan, 0));
5284
+ this._rowHeights = [];
5285
+ this._rowYPos = [this._position.y];
5286
+ this._rowBuffer = new Set();
5287
+ }
5288
+ function ensureColumnWidths(numCols) {
5289
+ let starColumnIndexes = [];
5290
+ let starMinAcc = 0;
5291
+ let unclaimedWidth = this._maxWidth;
5292
+ for (let i = 0; i < numCols; i++) {
5293
+ let col = this._colStyle(i);
5294
+ if (col.width === '*') {
5295
+ starColumnIndexes[i] = col;
5296
+ starMinAcc += col.minWidth;
5297
+ } else {
5298
+ unclaimedWidth -= col.width;
5299
+ this._columnWidths[i] = col.width;
5300
+ }
5301
+ }
5302
+ let starColCount = starColumnIndexes.reduce(x => x + 1, 0);
5303
+ if (starMinAcc >= unclaimedWidth) {
5304
+ starColumnIndexes.forEach((cell, i) => {
5305
+ this._columnWidths[i] = cell.minWidth;
5306
+ });
5307
+ } else if (starColCount > 0) {
5308
+ starColumnIndexes.forEach((col, i) => {
5309
+ let starSize = unclaimedWidth / starColCount;
5310
+ this._columnWidths[i] = Math.max(starSize, col.minWidth);
5311
+ if (col.maxWidth > 0) {
5312
+ this._columnWidths[i] = Math.min(this._columnWidths[i], col.maxWidth);
5313
+ }
5314
+ unclaimedWidth -= this._columnWidths[i];
5315
+ starColCount--;
5316
+ });
5317
+ }
5318
+ let tempX = this._position.x;
5319
+ this._columnXPos = Array.from(this._columnWidths, v => {
5320
+ const t = tempX;
5321
+ tempX += v;
5322
+ return t;
5323
+ });
5324
+ }
5325
+ function measure(row, rowIndex) {
5326
+ row.forEach(cell => this._rowBuffer.add(cell));
5327
+ if (rowIndex > 0) {
5328
+ this._rowYPos[rowIndex] = this._rowYPos[rowIndex - 1] + this._rowHeights[rowIndex - 1];
5329
+ }
5330
+ const rowStyle = this._rowStyle(rowIndex);
5331
+ let toRender = [];
5332
+ this._rowBuffer.forEach(cell => {
5333
+ if (cell.rowIndex + cell.rowSpan - 1 === rowIndex) {
5334
+ toRender.push(measureCell.call(this, cell, rowStyle.height));
5335
+ this._rowBuffer.delete(cell);
5336
+ }
5337
+ });
5338
+ let rowHeight = rowStyle.height;
5339
+ if (rowHeight === 'auto') {
5340
+ rowHeight = toRender.reduce((acc, cell) => {
5341
+ let minHeight = cell.textBounds.height + cell.padding.top + cell.padding.bottom;
5342
+ for (let i = 0; i < cell.rowSpan - 1; i++) {
5343
+ minHeight -= this._rowHeights[cell.rowIndex + i];
5344
+ }
5345
+ return Math.max(acc, minHeight);
5346
+ }, 0);
5347
+ }
5348
+ rowHeight = Math.max(rowHeight, rowStyle.minHeight);
5349
+ if (rowStyle.maxHeight > 0) {
5350
+ rowHeight = Math.min(rowHeight, rowStyle.maxHeight);
5351
+ }
5352
+ this._rowHeights[rowIndex] = rowHeight;
5353
+ let newPage = false;
5354
+ if (rowHeight > this.document.page.contentHeight) {
5355
+ console.warn(new Error(`Row ${rowIndex} requested more than the safe page height, row has been clamped`).stack.slice(7));
5356
+ this._rowHeights[rowIndex] = this.document.page.maxY() - this._rowYPos[rowIndex];
5357
+ } else if (this._rowYPos[rowIndex] + rowHeight >= this.document.page.maxY()) {
5358
+ this._rowYPos[rowIndex] = this.document.page.margins.top;
5359
+ newPage = true;
5360
+ }
5361
+ return {
5362
+ newPage,
5363
+ toRender: toRender.map(cell => measureCell.call(this, cell, rowHeight))
5364
+ };
5365
+ }
5366
+ function measureCell(cell, rowHeight) {
5367
+ let cellWidth = 0;
5368
+ for (let i = 0; i < cell.colSpan; i++) {
5369
+ cellWidth += this._columnWidths[cell.colIndex + i];
5370
+ }
5371
+ let cellHeight = rowHeight;
5372
+ if (cellHeight === 'auto') {
5373
+ cellHeight = this.document.page.contentHeight;
5374
+ } else {
5375
+ for (let i = 0; i < cell.rowSpan - 1; i++) {
5376
+ cellHeight += this._rowHeights[cell.rowIndex + i];
5377
+ }
5378
+ }
5379
+ const textAllocatedWidth = cellWidth - cell.padding.left - cell.padding.right;
5380
+ const textAllocatedHeight = cellHeight - cell.padding.top - cell.padding.bottom;
5381
+ const rotation = cell.textOptions.rotation ?? 0;
5382
+ const {
5383
+ width: textMaxWidth,
5384
+ height: textMaxHeight
5385
+ } = computeBounds(rotation, textAllocatedWidth, textAllocatedHeight);
5386
+ const textOptions = {
5387
+ align: cell.align.x,
5388
+ ellipsis: true,
5389
+ stroke: cell.textStroke > 0,
5390
+ fill: true,
5391
+ width: textMaxWidth,
5392
+ height: textMaxHeight,
5393
+ rotation,
5394
+ ...cell.textOptions
5395
+ };
5396
+ let textBounds = {
5397
+ x: 0,
5398
+ y: 0,
5399
+ width: 0,
5400
+ height: 0
5401
+ };
5402
+ if (cell.text) {
5403
+ const rollbackFont = this.document._fontSource;
5404
+ const rollbackFontSize = this.document._fontSize;
5405
+ const rollbackFontFamily = this.document._fontFamily;
5406
+ if (cell.font?.src) this.document.font(cell.font.src, cell.font?.family);
5407
+ if (cell.font?.size) this.document.fontSize(cell.font.size);
5408
+ const unRotatedTextBounds = this.document.boundsOfString(cell.text, 0, 0, {
5409
+ ...textOptions,
5410
+ rotation: 0
5411
+ });
5412
+ textOptions.width = unRotatedTextBounds.width;
5413
+ textOptions.height = unRotatedTextBounds.height;
5414
+ textBounds = this.document.boundsOfString(cell.text, 0, 0, textOptions);
5415
+ this.document.font(rollbackFont, rollbackFontFamily, rollbackFontSize);
5416
+ }
5417
+ return {
5418
+ ...cell,
5419
+ textOptions,
5420
+ x: this._columnXPos[cell.colIndex],
5421
+ y: this._rowYPos[cell.rowIndex],
5422
+ textX: this._columnXPos[cell.colIndex] + cell.padding.left,
5423
+ textY: this._rowYPos[cell.rowIndex] + cell.padding.top,
5424
+ width: cellWidth,
5425
+ height: cellHeight,
5426
+ textAllocatedHeight,
5427
+ textAllocatedWidth,
5428
+ textBounds
5429
+ };
5430
+ }
5431
+ function computeBounds(rotation, allocWidth, allocHeight) {
5432
+ let textMaxWidth, textMaxHeight;
5433
+ const cos = cosine(rotation);
5434
+ const sin = sine(rotation);
5435
+ if (rotation === 0 || rotation === 180) {
5436
+ textMaxWidth = allocWidth;
5437
+ textMaxHeight = allocHeight;
5438
+ } else if (rotation === 90 || rotation === 270) {
5439
+ textMaxWidth = allocHeight;
5440
+ textMaxHeight = allocWidth;
5441
+ } else if (rotation < 90 || rotation > 180 && rotation < 270) {
5442
+ textMaxWidth = allocWidth / (2 * cos);
5443
+ textMaxHeight = allocWidth / (2 * sin);
5444
+ } else {
5445
+ textMaxHeight = allocWidth / (2 * cos);
5446
+ textMaxWidth = allocWidth / (2 * sin);
5447
+ }
5448
+ const EF = sin * textMaxWidth;
5449
+ const FG = cos * textMaxHeight;
5450
+ if (EF + FG > allocHeight) {
5451
+ const denominator = cos * cos - sin * sin;
5452
+ if (rotation === 0 || rotation === 180) {
5453
+ textMaxWidth = allocWidth;
5454
+ textMaxHeight = allocHeight;
5455
+ } else if (rotation === 90 || rotation === 270) {
5456
+ textMaxWidth = allocHeight;
5457
+ textMaxHeight = allocWidth;
5458
+ } else if (rotation < 90 || rotation > 180 && rotation < 270) {
5459
+ textMaxWidth = (allocWidth * cos - allocHeight * sin) / denominator;
5460
+ textMaxHeight = (allocHeight * cos - allocWidth * sin) / denominator;
5461
+ } else {
5462
+ textMaxHeight = (allocWidth * cos - allocHeight * sin) / denominator;
5463
+ textMaxWidth = (allocHeight * cos - allocWidth * sin) / denominator;
5464
+ }
5465
+ }
5466
+ return {
5467
+ width: Math.abs(textMaxWidth),
5468
+ height: Math.abs(textMaxHeight)
5469
+ };
5470
+ }
5471
+
5472
+ function accommodateTable() {
5473
+ const structParent = this.opts.structParent;
5474
+ if (structParent) {
5475
+ this._tableStruct = this.document.struct('Table');
5476
+ this._tableStruct.dictionary.data.ID = this._id;
5477
+ if (structParent instanceof PDFStructureElement) {
5478
+ structParent.add(this._tableStruct);
5479
+ } else if (structParent instanceof PDFDocument) {
5480
+ structParent.addStructure(this._tableStruct);
5481
+ }
5482
+ this._headerRowLookup = {};
5483
+ this._headerColumnLookup = {};
5484
+ }
5485
+ }
5486
+ function accommodateCleanup() {
5487
+ if (this._tableStruct) this._tableStruct.end();
5488
+ }
5489
+ function accessibleRow(row, rowIndex, renderCell) {
5490
+ const rowStruct = this.document.struct('TR');
5491
+ rowStruct.dictionary.data.ID = new String(`${this._id}-${rowIndex}`);
5492
+ this._tableStruct.add(rowStruct);
5493
+ row.forEach(cell => renderCell(cell, rowStruct));
5494
+ rowStruct.end();
5495
+ }
5496
+ function accessibleCell(cell, rowStruct, callback) {
5497
+ const doc = this.document;
5498
+ const cellStruct = doc.struct(cell.type, {
5499
+ title: cell.title
5500
+ });
5501
+ cellStruct.dictionary.data.ID = cell.id;
5502
+ rowStruct.add(cellStruct);
5503
+ const padding = cell.padding;
5504
+ const border = cell.border;
5505
+ const attributes = {
5506
+ O: 'Table',
5507
+ Width: cell.width,
5508
+ Height: cell.height,
5509
+ Padding: [padding.top, padding.bottom, padding.left, padding.right],
5510
+ RowSpan: cell.rowSpan > 1 ? cell.rowSpan : undefined,
5511
+ ColSpan: cell.colSpan > 1 ? cell.colSpan : undefined,
5512
+ BorderThickness: [border.top, border.bottom, border.left, border.right]
5513
+ };
5514
+ if (cell.type === 'TH') {
5515
+ if (cell.scope === 'Row' || cell.scope === 'Both') {
5516
+ for (let i = 0; i < cell.rowSpan; i++) {
5517
+ if (!this._headerRowLookup[cell.rowIndex + i]) {
5518
+ this._headerRowLookup[cell.rowIndex + i] = [];
5519
+ }
5520
+ this._headerRowLookup[cell.rowIndex + i].push(cell.id);
5521
+ }
5522
+ attributes.Scope = cell.scope;
5523
+ }
5524
+ if (cell.scope === 'Column' || cell.scope === 'Both') {
5525
+ for (let i = 0; i < cell.colSpan; i++) {
5526
+ if (!this._headerColumnLookup[cell.colIndex + i]) {
5527
+ this._headerColumnLookup[cell.colIndex + i] = [];
5528
+ }
5529
+ this._headerColumnLookup[cell.colIndex + i].push(cell.id);
5530
+ }
5531
+ attributes.Scope = cell.scope;
5532
+ }
5533
+ }
5534
+ const Headers = new Set([...Array.from({
5535
+ length: cell.colSpan
5536
+ }, (_, i) => this._headerColumnLookup[cell.colIndex + i]).flat(), ...Array.from({
5537
+ length: cell.rowSpan
5538
+ }, (_, i) => this._headerRowLookup[cell.rowIndex + i]).flat()].filter(Boolean));
5539
+ if (Headers.size) attributes.Headers = Array.from(Headers);
5540
+ const normalizeColor = doc._normalizeColor;
5541
+ if (cell.backgroundColor != null) {
5542
+ attributes.BackgroundColor = normalizeColor(cell.backgroundColor);
5543
+ }
5544
+ const hasBorder = [border.top, border.bottom, border.left, border.right];
5545
+ if (hasBorder.some(x => x)) {
5546
+ const borderColor = cell.borderColor;
5547
+ 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];
5548
+ }
5549
+ Object.keys(attributes).forEach(key => attributes[key] === undefined && delete attributes[key]);
5550
+ cellStruct.dictionary.data.A = doc.ref(attributes);
5551
+ cellStruct.add(callback);
5552
+ cellStruct.end();
5553
+ cellStruct.dictionary.data.A.end();
5554
+ }
5555
+
5556
+ function renderRow(row, rowIndex) {
5557
+ if (this._tableStruct) {
5558
+ accessibleRow.call(this, row, rowIndex, renderCell.bind(this));
5559
+ } else {
5560
+ row.forEach(cell => renderCell.call(this, cell));
5561
+ }
5562
+ return this._rowYPos[rowIndex] + this._rowHeights[rowIndex];
5563
+ }
5564
+ function renderCell(cell, rowStruct) {
5565
+ const cellRenderer = () => {
5566
+ if (cell.backgroundColor != null) {
5567
+ this.document.save().rect(cell.x, cell.y, cell.width, cell.height).fill(cell.backgroundColor).restore();
5568
+ }
5569
+ renderBorder.call(this, cell.border, cell.borderColor, cell.x, cell.y, cell.width, cell.height);
5570
+ if (cell.debug) {
5571
+ this.document.save();
5572
+ this.document.dash(1, {
5573
+ space: 1
5574
+ }).lineWidth(1).strokeOpacity(0.3);
5575
+ this.document.rect(cell.x, cell.y, cell.width, cell.height).stroke('green');
5576
+ this.document.restore();
5577
+ }
5578
+ if (cell.text) renderCellText.call(this, cell);
5579
+ };
5580
+ if (rowStruct) accessibleCell.call(this, cell, rowStruct, cellRenderer);else cellRenderer();
5581
+ }
5582
+ function renderCellText(cell) {
5583
+ const doc = this.document;
5584
+ const rollbackFont = doc._fontSource;
5585
+ const rollbackFontSize = doc._fontSize;
5586
+ const rollbackFontFamily = doc._fontFamily;
5587
+ if (cell.customFont) {
5588
+ if (cell.font.src) doc.font(cell.font.src, cell.font.family);
5589
+ if (cell.font.size) doc.fontSize(cell.font.size);
5590
+ }
5591
+ const x = cell.textX;
5592
+ const y = cell.textY;
5593
+ const Ah = cell.textAllocatedHeight;
5594
+ const Aw = cell.textAllocatedWidth;
5595
+ const Cw = cell.textBounds.width;
5596
+ const Ch = cell.textBounds.height;
5597
+ const Ox = -cell.textBounds.x;
5598
+ const Oy = -cell.textBounds.y;
5599
+ const PxScale = cell.align.x === 'right' ? 1 : cell.align.x === 'center' ? 0.5 : 0;
5600
+ const Px = (Aw - Cw) * PxScale;
5601
+ const PyScale = cell.align.y === 'bottom' ? 1 : cell.align.y === 'center' ? 0.5 : 0;
5602
+ const Py = (Ah - Ch) * PyScale;
5603
+ const dx = Px + Ox;
5604
+ const dy = Py + Oy;
5605
+ if (cell.debug) {
5606
+ doc.save();
5607
+ doc.dash(1, {
5608
+ space: 1
5609
+ }).lineWidth(1).strokeOpacity(0.3);
5610
+ if (cell.text) {
5611
+ 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');
5612
+ }
5613
+ doc.rect(x, y, Aw, Ah).stroke('orange');
5614
+ doc.restore();
5615
+ }
5616
+ doc.save().rect(x, y, Aw, Ah).clip();
5617
+ doc.fillColor(cell.textColor).strokeColor(cell.textStrokeColor);
5618
+ if (cell.textStroke > 0) doc.lineWidth(cell.textStroke);
5619
+ doc.text(cell.text, x + dx, y + dy, cell.textOptions);
5620
+ doc.restore();
5621
+ if (cell.font) doc.font(rollbackFont, rollbackFontFamily, rollbackFontSize);
5622
+ }
5623
+ function renderBorder(border, borderColor, x, y, width, height, mask) {
5624
+ border = Object.fromEntries(Object.entries(border).map(_ref => {
5625
+ let [k, v] = _ref;
5626
+ return [k, mask && !mask[k] ? 0 : v];
5627
+ }));
5628
+ const doc = this.document;
5629
+ if ([border.right, border.bottom, border.left].every(val => val === border.top)) {
5630
+ if (border.top > 0) {
5631
+ doc.save().lineWidth(border.top).rect(x, y, width, height).stroke(borderColor.top).restore();
5632
+ }
5633
+ } else {
5634
+ if (border.top > 0) {
5635
+ doc.save().lineWidth(border.top).moveTo(x, y).lineTo(x + width, y).stroke(borderColor.top).restore();
5636
+ }
5637
+ if (border.right > 0) {
5638
+ doc.save().lineWidth(border.right).moveTo(x + width, y).lineTo(x + width, y + height).stroke(borderColor.right).restore();
5639
+ }
5640
+ if (border.bottom > 0) {
5641
+ doc.save().lineWidth(border.bottom).moveTo(x + width, y + height).lineTo(x, y + height).stroke(borderColor.bottom).restore();
5642
+ }
5643
+ if (border.left > 0) {
5644
+ doc.save().lineWidth(border.left).moveTo(x, y + height).lineTo(x, y).stroke(borderColor.left).restore();
5645
+ }
5646
+ }
5647
+ }
5648
+
5649
+ class PDFTable {
5650
+ constructor(document) {
5651
+ let opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
5652
+ this.document = document;
5653
+ this.opts = Object.freeze(opts);
5654
+ normalizeTable.call(this);
5655
+ accommodateTable.call(this);
5656
+ this._currRowIndex = 0;
5657
+ this._ended = false;
5658
+ if (opts.data) {
5659
+ for (const row of opts.data) this.row(row);
5660
+ return this.end();
5661
+ }
5662
+ }
5663
+ row(row) {
5664
+ let lastRow = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
5665
+ if (this._ended) {
5666
+ throw new Error(`Table was marked as ended on row ${this._currRowIndex}`);
5667
+ }
5668
+ row = Array.from(row);
5669
+ row = normalizeRow.call(this, row, this._currRowIndex);
5670
+ if (this._currRowIndex === 0) ensure.call(this, row);
5671
+ const {
5672
+ newPage,
5673
+ toRender
5674
+ } = measure.call(this, row, this._currRowIndex);
5675
+ if (newPage) this.document.continueOnNewPage();
5676
+ const yPos = renderRow.call(this, toRender, this._currRowIndex);
5677
+ this.document.x = this._position.x;
5678
+ this.document.y = yPos;
5679
+ if (lastRow) return this.end();
5680
+ this._currRowIndex++;
5681
+ return this;
5682
+ }
5683
+ end() {
5684
+ while (this._rowBuffer?.size) this.row([]);
5685
+ this._ended = true;
5686
+ accommodateCleanup.call(this);
5687
+ return this.document;
5688
+ }
5689
+ }
5690
+
5691
+ var TableMixin = {
5692
+ initTables() {
5693
+ this._tableIndex = 0;
5694
+ },
5695
+ table(opts) {
5696
+ return new PDFTable(this, opts);
5697
+ }
5698
+ };
5699
+
5440
5700
  class PDFMetadata {
5441
5701
  constructor() {
5442
5702
  this._metadata = `
@@ -5480,7 +5740,7 @@ var MetadataMixin = {
5480
5740
  _addInfo() {
5481
5741
  this.appendXML(`
5482
5742
  <rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/">
5483
- <xmp:CreateDate>${this.info.CreationDate.toISOString().split('.')[0] + "Z"}</xmp:CreateDate>
5743
+ <xmp:CreateDate>${this.info.CreationDate.toISOString().split('.')[0] + 'Z'}</xmp:CreateDate>
5484
5744
  <xmp:CreatorTool>${this.info.Creator}</xmp:CreatorTool>
5485
5745
  </rdf:Description>
5486
5746
  `);
@@ -5533,11 +5793,6 @@ var MetadataMixin = {
5533
5793
  endMetadata() {
5534
5794
  this._addInfo();
5535
5795
  this.metadata.end();
5536
-
5537
- /*
5538
- Metadata was introduced in PDF 1.4, so adding it to 1.3
5539
- will likely only take up more space.
5540
- */
5541
5796
  if (this.version != 1.3) {
5542
5797
  this.metadataRef = this.ref({
5543
5798
  length: this.metadata.getLength(),
@@ -5552,17 +5807,11 @@ var MetadataMixin = {
5552
5807
  }
5553
5808
  };
5554
5809
 
5555
- /*
5556
- PDFDocument - represents an entire PDF document
5557
- By Devon Govett
5558
- */
5559
5810
  class PDFDocument extends stream.Readable {
5560
5811
  constructor() {
5561
5812
  let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
5562
5813
  super(options);
5563
5814
  this.options = options;
5564
-
5565
- // PDF version
5566
5815
  switch (options.pdfVersion) {
5567
5816
  case '1.4':
5568
5817
  this.version = 1.4;
@@ -5581,13 +5830,9 @@ class PDFDocument extends stream.Readable {
5581
5830
  this.version = 1.3;
5582
5831
  break;
5583
5832
  }
5584
-
5585
- // Whether streams should be compressed
5586
5833
  this.compress = this.options.compress != null ? this.options.compress : true;
5587
5834
  this._pageBuffer = [];
5588
5835
  this._pageBufferStart = 0;
5589
-
5590
- // The PDF object store
5591
5836
  this._offsets = [];
5592
5837
  this._waiting = 0;
5593
5838
  this._ended = false;
@@ -5608,11 +5853,7 @@ class PDFDocument extends stream.Readable {
5608
5853
  if (this.options.lang) {
5609
5854
  this._root.data.Lang = new String(this.options.lang);
5610
5855
  }
5611
-
5612
- // The current page
5613
5856
  this.page = null;
5614
-
5615
- // Initialize mixins
5616
5857
  this.initMetadata();
5617
5858
  this.initColor();
5618
5859
  this.initVector();
@@ -5621,9 +5862,8 @@ class PDFDocument extends stream.Readable {
5621
5862
  this.initImages();
5622
5863
  this.initOutline();
5623
5864
  this.initMarkings(options);
5865
+ this.initTables();
5624
5866
  this.initSubset(options);
5625
-
5626
- // Initialize the metadata
5627
5867
  this.info = {
5628
5868
  Producer: 'PDFKit',
5629
5869
  Creator: 'PDFKit',
@@ -5640,21 +5880,10 @@ class PDFDocument extends stream.Readable {
5640
5880
  DisplayDocTitle: true
5641
5881
  });
5642
5882
  }
5643
-
5644
- // Generate file ID
5645
5883
  this._id = PDFSecurity.generateFileID(this.info);
5646
-
5647
- // Initialize security settings
5648
5884
  this._security = PDFSecurity.create(this, options);
5649
-
5650
- // Write the header
5651
- // PDF version
5652
5885
  this._write(`%PDF-${this.version}`);
5653
-
5654
- // 4 binary chars, as recommended by the spec
5655
5886
  this._write('%\xFF\xFF\xFF\xFF');
5656
-
5657
- // Add the first page
5658
5887
  if (this.options.autoFirstPage !== false) {
5659
5888
  this.addPage();
5660
5889
  }
@@ -5665,27 +5894,16 @@ class PDFDocument extends stream.Readable {
5665
5894
  options
5666
5895
  } = this);
5667
5896
  }
5668
-
5669
- // end the current page if needed
5670
5897
  if (!this.options.bufferPages) {
5671
5898
  this.flushPages();
5672
5899
  }
5673
-
5674
- // create a page object
5675
5900
  this.page = new PDFPage(this, options);
5676
5901
  this._pageBuffer.push(this.page);
5677
-
5678
- // add the page to the object store
5679
5902
  const pages = this._root.data.Pages.data;
5680
5903
  pages.Kids.push(this.page.dictionary);
5681
5904
  pages.Count++;
5682
-
5683
- // reset x and y coordinates
5684
5905
  this.x = this.page.margins.left;
5685
5906
  this.y = this.page.margins.top;
5686
-
5687
- // flip PDF coordinate system so that the origin is in
5688
- // the top left rather than the bottom left
5689
5907
  this._ctm = [1, 0, 0, 1, 0, 0];
5690
5908
  this.transform(1, 0, 0, -1, 0, this.page.height);
5691
5909
  this.emit('pageAdded');
@@ -5693,7 +5911,7 @@ class PDFDocument extends stream.Readable {
5693
5911
  }
5694
5912
  continueOnNewPage(options) {
5695
5913
  const pageMarkings = this.endPageMarkings(this.page);
5696
- this.addPage(options);
5914
+ this.addPage(options ?? this.page._options);
5697
5915
  this.initPageMarkings(pageMarkings);
5698
5916
  return this;
5699
5917
  }
@@ -5711,8 +5929,6 @@ class PDFDocument extends stream.Readable {
5711
5929
  return this.page = page;
5712
5930
  }
5713
5931
  flushPages() {
5714
- // this local variable exists so we're future-proof against
5715
- // reentrant calls to flushPages.
5716
5932
  const pages = this._pageBuffer;
5717
5933
  this._pageBuffer = [];
5718
5934
  this._pageBufferStart += pages.length;
@@ -5736,13 +5952,10 @@ class PDFDocument extends stream.Readable {
5736
5952
  }
5737
5953
  addNamedEmbeddedFile(name, ref) {
5738
5954
  if (!this._root.data.Names.data.EmbeddedFiles) {
5739
- // disabling /Limits for this tree fixes attachments not showing in Adobe Reader
5740
5955
  this._root.data.Names.data.EmbeddedFiles = new PDFNameTree({
5741
5956
  limits: false
5742
5957
  });
5743
5958
  }
5744
-
5745
- // add filespec to EmbeddedFiles
5746
5959
  this._root.data.Names.data.EmbeddedFiles.add(name, ref);
5747
5960
  }
5748
5961
  addNamedJavaScript(name, js) {
@@ -5757,19 +5970,17 @@ class PDFDocument extends stream.Readable {
5757
5970
  }
5758
5971
  ref(data) {
5759
5972
  const ref = new PDFReference(this, this._offsets.length + 1, data);
5760
- this._offsets.push(null); // placeholder for this object's offset once it is finalized
5973
+ this._offsets.push(null);
5761
5974
  this._waiting++;
5762
5975
  return ref;
5763
5976
  }
5764
5977
  _read() {}
5765
- // do nothing, but this method is required by node
5766
-
5767
5978
  _write(data) {
5768
5979
  if (!Buffer.isBuffer(data)) {
5769
5980
  data = Buffer.from(data + '\n', 'binary');
5770
5981
  }
5771
5982
  this.push(data);
5772
- return this._offset += data.length;
5983
+ this._offset += data.length;
5773
5984
  }
5774
5985
  addContent(data) {
5775
5986
  this.page.write(data);
@@ -5779,7 +5990,7 @@ class PDFDocument extends stream.Readable {
5779
5990
  this._offsets[ref.id - 1] = ref.offset;
5780
5991
  if (--this._waiting === 0 && this._ended) {
5781
5992
  this._finalize();
5782
- return this._ended = false;
5993
+ this._ended = false;
5783
5994
  }
5784
5995
  }
5785
5996
  end() {
@@ -5816,13 +6027,12 @@ class PDFDocument extends stream.Readable {
5816
6027
  this._security.end();
5817
6028
  }
5818
6029
  if (this._waiting === 0) {
5819
- return this._finalize();
6030
+ this._finalize();
5820
6031
  } else {
5821
- return this._ended = true;
6032
+ this._ended = true;
5822
6033
  }
5823
6034
  }
5824
6035
  _finalize() {
5825
- // generate xref
5826
6036
  const xRefOffset = this._offset;
5827
6037
  this._write('xref');
5828
6038
  this._write(`0 ${this._offsets.length + 1}`);
@@ -5831,8 +6041,6 @@ class PDFDocument extends stream.Readable {
5831
6041
  offset = `0000000000${offset}`.slice(-10);
5832
6042
  this._write(offset + ' 00000 n ');
5833
6043
  }
5834
-
5835
- // trailer
5836
6044
  const trailer = {
5837
6045
  Size: this._offsets.length + 1,
5838
6046
  Root: this._root,
@@ -5847,9 +6055,7 @@ class PDFDocument extends stream.Readable {
5847
6055
  this._write('startxref');
5848
6056
  this._write(`${xRefOffset}`);
5849
6057
  this._write('%%EOF');
5850
-
5851
- // end the stream
5852
- return this.push(null);
6058
+ this.push(null);
5853
6059
  }
5854
6060
  toString() {
5855
6061
  return '[object PDFDocument]';
@@ -5870,6 +6076,7 @@ mixin(MarkingsMixin);
5870
6076
  mixin(AcroFormMixin);
5871
6077
  mixin(AttachmentsMixin);
5872
6078
  mixin(SubsetMixin);
6079
+ mixin(TableMixin);
5873
6080
  PDFDocument.LineWrapper = LineWrapper;
5874
6081
 
5875
6082
  export { PDFDocument as default };