pdfkit 0.15.2 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/js/pdfkit.js CHANGED
@@ -10,23 +10,15 @@ var LineBreaker = require('linebreak');
10
10
  var exif = require('jpeg-exif');
11
11
  var PNG = require('png-js');
12
12
 
13
- /*
14
- PDFAbstractReference - abstract class for PDF reference
15
- */
16
-
17
13
  class PDFAbstractReference {
18
14
  toString() {
19
15
  throw new Error('Must be implemented by subclasses');
20
16
  }
21
17
  }
22
18
 
23
- /*
24
- PDFTree - abstract base class for name and number tree objects
25
- */
26
19
  class PDFTree {
27
20
  constructor(options = {}) {
28
21
  this._items = {};
29
- // disable /Limits output for this tree
30
22
  this.limits = typeof options.limits === 'boolean' ? options.limits : true;
31
23
  }
32
24
  add(key, val) {
@@ -36,7 +28,6 @@ class PDFTree {
36
28
  return this._items[key];
37
29
  }
38
30
  toString() {
39
- // Needs to be sorted by key
40
31
  const sortedKeys = Object.keys(this._items).sort((a, b) => this._compareKeys(a, b));
41
32
  const out = ['<<'];
42
33
  if (this.limits && sortedKeys.length > 1) {
@@ -52,23 +43,37 @@ class PDFTree {
52
43
  out.push('>>');
53
44
  return out.join('\n');
54
45
  }
55
- _compareKeys( /*a, b*/
56
- ) {
46
+ _compareKeys() {
57
47
  throw new Error('Must be implemented by subclasses');
58
48
  }
59
49
  _keysName() {
60
50
  throw new Error('Must be implemented by subclasses');
61
51
  }
62
- _dataForKey( /*k*/
63
- ) {
52
+ _dataForKey() {
64
53
  throw new Error('Must be implemented by subclasses');
65
54
  }
66
55
  }
67
56
 
68
- /*
69
- PDFObject - converts JavaScript types into their corresponding PDF types.
70
- By Devon Govett
71
- */
57
+ class SpotColor {
58
+ constructor(doc, name, C, M, Y, K) {
59
+ this.id = 'CS' + Object.keys(doc.spotColors).length;
60
+ this.name = name;
61
+ this.values = [C, M, Y, K];
62
+ this.ref = doc.ref(['Separation', this.name, 'DeviceCMYK', {
63
+ Range: [0, 1, 0, 1, 0, 1, 0, 1],
64
+ C0: [0, 0, 0, 0],
65
+ C1: this.values.map(value => value / 100),
66
+ FunctionType: 2,
67
+ Domain: [0, 1],
68
+ N: 1
69
+ }]);
70
+ this.ref.end();
71
+ }
72
+ toString() {
73
+ return `${this.ref.id} 0 R`;
74
+ }
75
+ }
76
+
72
77
  const pad = (str, length) => (Array(length + 1).join('0') + str).slice(-length);
73
78
  const escapableRe = /[\n\r\t\b\f()\\]/g;
74
79
  const escapable = {
@@ -81,8 +86,6 @@ const escapable = {
81
86
  '(': '\\(',
82
87
  ')': '\\)'
83
88
  };
84
-
85
- // Convert little endian UTF-16 to big endian
86
89
  const swapBytes = function (buff) {
87
90
  const l = buff.length;
88
91
  if (l & 0x01) {
@@ -98,14 +101,10 @@ const swapBytes = function (buff) {
98
101
  };
99
102
  class PDFObject {
100
103
  static convert(object, encryptFn = null) {
101
- // String literals are converted to the PDF name type
102
104
  if (typeof object === 'string') {
103
105
  return `/${object}`;
104
-
105
- // String objects are converted to PDF strings (UTF-16)
106
106
  } else if (object instanceof String) {
107
107
  let string = object;
108
- // Detect if this is a unicode string
109
108
  let isUnicode = false;
110
109
  for (let i = 0, end = string.length; i < end; i++) {
111
110
  if (string.charCodeAt(i) > 0x7f) {
@@ -113,39 +112,27 @@ class PDFObject {
113
112
  break;
114
113
  }
115
114
  }
116
-
117
- // If so, encode it as big endian UTF-16
118
115
  let stringBuffer;
119
116
  if (isUnicode) {
120
117
  stringBuffer = swapBytes(Buffer.from(`\ufeff${string}`, 'utf16le'));
121
118
  } else {
122
119
  stringBuffer = Buffer.from(string.valueOf(), 'ascii');
123
120
  }
124
-
125
- // Encrypt the string when necessary
126
121
  if (encryptFn) {
127
122
  string = encryptFn(stringBuffer).toString('binary');
128
123
  } else {
129
124
  string = stringBuffer.toString('binary');
130
125
  }
131
-
132
- // Escape characters as required by the spec
133
126
  string = string.replace(escapableRe, c => escapable[c]);
134
127
  return `(${string})`;
135
-
136
- // Buffers are converted to PDF hex strings
137
128
  } else if (Buffer.isBuffer(object)) {
138
129
  return `<${object.toString('hex')}>`;
139
- } else if (object instanceof PDFAbstractReference || object instanceof PDFTree) {
130
+ } else if (object instanceof PDFAbstractReference || object instanceof PDFTree || object instanceof SpotColor) {
140
131
  return object.toString();
141
132
  } else if (object instanceof Date) {
142
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';
143
-
144
- // Encrypt the string when necessary
145
134
  if (encryptFn) {
146
135
  string = encryptFn(Buffer.from(string, 'ascii')).toString('binary');
147
-
148
- // Escape characters as required by the spec
149
136
  string = string.replace(escapableRe, c => escapable[c]);
150
137
  }
151
138
  return `(${string})`;
@@ -174,10 +161,6 @@ class PDFObject {
174
161
  }
175
162
  }
176
163
 
177
- /*
178
- PDFReference - represents a reference to another object in the PDF object heirarchy
179
- By Devon Govett
180
- */
181
164
  class PDFReference extends PDFAbstractReference {
182
165
  constructor(document, id, data = {}) {
183
166
  super();
@@ -190,7 +173,7 @@ class PDFReference extends PDFAbstractReference {
190
173
  this.buffer = [];
191
174
  }
192
175
  write(chunk) {
193
- if (!Buffer.isBuffer(chunk)) {
176
+ if (!(chunk instanceof Uint8Array)) {
194
177
  chunk = Buffer.from(chunk + '\n', 'binary');
195
178
  }
196
179
  this.uncompressedLength += chunk.length;
@@ -200,14 +183,14 @@ class PDFReference extends PDFAbstractReference {
200
183
  this.buffer.push(chunk);
201
184
  this.data.Length += chunk.length;
202
185
  if (this.compress) {
203
- return this.data.Filter = 'FlateDecode';
186
+ this.data.Filter = 'FlateDecode';
204
187
  }
205
188
  }
206
189
  end(chunk) {
207
190
  if (chunk) {
208
191
  this.write(chunk);
209
192
  }
210
- return this.finalize();
193
+ this.finalize();
211
194
  }
212
195
  finalize() {
213
196
  this.offset = this.document._offset;
@@ -227,7 +210,7 @@ class PDFReference extends PDFAbstractReference {
227
210
  if (this.buffer.length) {
228
211
  this.document._write('stream');
229
212
  this.document._write(this.buffer);
230
- this.buffer = []; // free up memory
213
+ this.buffer = [];
231
214
  this.document._write('\nendstream');
232
215
  }
233
216
  this.document._write('endobj');
@@ -238,10 +221,69 @@ class PDFReference extends PDFAbstractReference {
238
221
  }
239
222
  }
240
223
 
241
- /*
242
- PDFPage - represents a single page in the PDF document
243
- By Devon Govett
244
- */
224
+ function PDFNumber(n) {
225
+ return Math.fround(n);
226
+ }
227
+ function normalizeSides(sides, defaultDefinition = undefined, transformer = v => v) {
228
+ if (sides == null || typeof sides === 'object' && Object.keys(sides).length === 0) {
229
+ sides = defaultDefinition;
230
+ }
231
+ if (sides == null || typeof sides !== 'object') {
232
+ sides = {
233
+ top: sides,
234
+ right: sides,
235
+ bottom: sides,
236
+ left: sides
237
+ };
238
+ } else if (Array.isArray(sides)) {
239
+ if (sides.length === 2) {
240
+ sides = {
241
+ vertical: sides[0],
242
+ horizontal: sides[1]
243
+ };
244
+ } else {
245
+ sides = {
246
+ top: sides[0],
247
+ right: sides[1],
248
+ bottom: sides[2],
249
+ left: sides[3]
250
+ };
251
+ }
252
+ }
253
+ if ('vertical' in sides || 'horizontal' in sides) {
254
+ sides = {
255
+ top: sides.vertical,
256
+ right: sides.horizontal,
257
+ bottom: sides.vertical,
258
+ left: sides.horizontal
259
+ };
260
+ }
261
+ return {
262
+ top: transformer(sides.top),
263
+ right: transformer(sides.right),
264
+ bottom: transformer(sides.bottom),
265
+ left: transformer(sides.left)
266
+ };
267
+ }
268
+ const MM_TO_CM = 1 / 10;
269
+ const CM_TO_IN = 1 / 2.54;
270
+ const PX_TO_IN = 1 / 96;
271
+ const IN_TO_PT = 72;
272
+ const PC_TO_PT = 12;
273
+ function cosine(a) {
274
+ if (a === 0) return 1;
275
+ if (a === 90) return 0;
276
+ if (a === 180) return -1;
277
+ if (a === 270) return 0;
278
+ return Math.cos(a * Math.PI / 180);
279
+ }
280
+ function sine(a) {
281
+ if (a === 0) return 0;
282
+ if (a === 90) return 1;
283
+ if (a === 180) return 0;
284
+ if (a === 270) return -1;
285
+ return Math.sin(a * Math.PI / 180);
286
+ }
245
287
 
246
288
  const DEFAULT_MARGINS = {
247
289
  top: 72,
@@ -304,35 +346,19 @@ const SIZES = {
304
346
  class PDFPage {
305
347
  constructor(document, options = {}) {
306
348
  this.document = document;
349
+ this._options = options;
307
350
  this.size = options.size || 'letter';
308
351
  this.layout = options.layout || 'portrait';
309
-
310
- // process margins
311
- if (typeof options.margin === 'number') {
312
- this.margins = {
313
- top: options.margin,
314
- left: options.margin,
315
- bottom: options.margin,
316
- right: options.margin
317
- };
318
-
319
- // default to 1 inch margins
320
- } else {
321
- this.margins = options.margins || DEFAULT_MARGINS;
322
- }
323
-
324
- // calculate page dimensions
325
352
  const dimensions = Array.isArray(this.size) ? this.size : SIZES[this.size.toUpperCase()];
326
353
  this.width = dimensions[this.layout === 'portrait' ? 0 : 1];
327
354
  this.height = dimensions[this.layout === 'portrait' ? 1 : 0];
328
355
  this.content = this.document.ref();
329
-
330
- // Initialize the Font, XObject, and ExtGState dictionaries
356
+ if (options.font) document.font(options.font, options.fontFamily);
357
+ if (options.fontSize) document.fontSize(options.fontSize);
358
+ this.margins = normalizeSides(options.margin ?? options.margins, DEFAULT_MARGINS, x => document.sizeToPoint(x, 0, this));
331
359
  this.resources = this.document.ref({
332
360
  ProcSet: ['PDF', 'Text', 'ImageB', 'ImageC', 'ImageI']
333
361
  });
334
-
335
- // The page dictionary
336
362
  this.dictionary = this.document.ref({
337
363
  Type: 'Page',
338
364
  Parent: this.document._root.data.Pages,
@@ -342,8 +368,6 @@ class PDFPage {
342
368
  });
343
369
  this.markings = [];
344
370
  }
345
-
346
- // Lazily create these objects
347
371
  get fonts() {
348
372
  const data = this.resources.data;
349
373
  return data.Font != null ? data.Font : data.Font = {};
@@ -372,204 +396,86 @@ class PDFPage {
372
396
  const data = this.dictionary.data;
373
397
  return data.StructParents != null ? data.StructParents : data.StructParents = this.document.createStructParentTreeNextKey();
374
398
  }
399
+ get contentWidth() {
400
+ return this.width - this.margins.left - this.margins.right;
401
+ }
402
+ get contentHeight() {
403
+ return this.height - this.margins.top - this.margins.bottom;
404
+ }
375
405
  maxY() {
376
406
  return this.height - this.margins.bottom;
377
407
  }
378
408
  write(chunk) {
379
409
  return this.content.write(chunk);
380
410
  }
411
+ _setTabOrder() {
412
+ if (!this.dictionary.Tabs && this.document.hasMarkInfoDictionary()) {
413
+ this.dictionary.data.Tabs = 'S';
414
+ }
415
+ }
381
416
  end() {
417
+ this._setTabOrder();
382
418
  this.dictionary.end();
419
+ this.resources.data.ColorSpace = this.resources.data.ColorSpace || {};
420
+ for (let color of Object.values(this.document.spotColors)) {
421
+ this.resources.data.ColorSpace[color.id] = color;
422
+ }
383
423
  this.resources.end();
384
424
  return this.content.end();
385
425
  }
386
426
  }
387
427
 
388
- /*
389
- PDFNameTree - represents a name tree object
390
- */
391
428
  class PDFNameTree extends PDFTree {
392
429
  _compareKeys(a, b) {
393
430
  return a.localeCompare(b);
394
431
  }
395
432
  _keysName() {
396
- return "Names";
433
+ return 'Names';
397
434
  }
398
435
  _dataForKey(k) {
399
436
  return new String(k);
400
437
  }
401
438
  }
402
439
 
403
- /**
404
- * Check if value is in a range group.
405
- * @param {number} value
406
- * @param {number[]} rangeGroup
407
- * @returns {boolean}
408
- */
409
440
  function inRange(value, rangeGroup) {
410
441
  if (value < rangeGroup[0]) return false;
411
442
  let startRange = 0;
412
443
  let endRange = rangeGroup.length / 2;
413
444
  while (startRange <= endRange) {
414
445
  const middleRange = Math.floor((startRange + endRange) / 2);
415
-
416
- // actual array index
417
446
  const arrayIndex = middleRange * 2;
418
-
419
- // Check if value is in range pointed by actual index
420
447
  if (value >= rangeGroup[arrayIndex] && value <= rangeGroup[arrayIndex + 1]) {
421
448
  return true;
422
449
  }
423
450
  if (value > rangeGroup[arrayIndex + 1]) {
424
- // Search Right Side Of Array
425
451
  startRange = middleRange + 1;
426
452
  } else {
427
- // Search Left Side Of Array
428
453
  endRange = middleRange - 1;
429
454
  }
430
455
  }
431
456
  return false;
432
457
  }
433
458
 
434
- // prettier-ignore-start
435
- /**
436
- * A.1 Unassigned code points in Unicode 3.2
437
- * @link https://tools.ietf.org/html/rfc3454#appendix-A.1
438
- */
439
459
  const unassigned_code_points = [0x0221, 0x0221, 0x0234, 0x024f, 0x02ae, 0x02af, 0x02ef, 0x02ff, 0x0350, 0x035f, 0x0370, 0x0373, 0x0376, 0x0379, 0x037b, 0x037d, 0x037f, 0x0383, 0x038b, 0x038b, 0x038d, 0x038d, 0x03a2, 0x03a2, 0x03cf, 0x03cf, 0x03f7, 0x03ff, 0x0487, 0x0487, 0x04cf, 0x04cf, 0x04f6, 0x04f7, 0x04fa, 0x04ff, 0x0510, 0x0530, 0x0557, 0x0558, 0x0560, 0x0560, 0x0588, 0x0588, 0x058b, 0x0590, 0x05a2, 0x05a2, 0x05ba, 0x05ba, 0x05c5, 0x05cf, 0x05eb, 0x05ef, 0x05f5, 0x060b, 0x060d, 0x061a, 0x061c, 0x061e, 0x0620, 0x0620, 0x063b, 0x063f, 0x0656, 0x065f, 0x06ee, 0x06ef, 0x06ff, 0x06ff, 0x070e, 0x070e, 0x072d, 0x072f, 0x074b, 0x077f, 0x07b2, 0x0900, 0x0904, 0x0904, 0x093a, 0x093b, 0x094e, 0x094f, 0x0955, 0x0957, 0x0971, 0x0980, 0x0984, 0x0984, 0x098d, 0x098e, 0x0991, 0x0992, 0x09a9, 0x09a9, 0x09b1, 0x09b1, 0x09b3, 0x09b5, 0x09ba, 0x09bb, 0x09bd, 0x09bd, 0x09c5, 0x09c6, 0x09c9, 0x09ca, 0x09ce, 0x09d6, 0x09d8, 0x09db, 0x09de, 0x09de, 0x09e4, 0x09e5, 0x09fb, 0x0a01, 0x0a03, 0x0a04, 0x0a0b, 0x0a0e, 0x0a11, 0x0a12, 0x0a29, 0x0a29, 0x0a31, 0x0a31, 0x0a34, 0x0a34, 0x0a37, 0x0a37, 0x0a3a, 0x0a3b, 0x0a3d, 0x0a3d, 0x0a43, 0x0a46, 0x0a49, 0x0a4a, 0x0a4e, 0x0a58, 0x0a5d, 0x0a5d, 0x0a5f, 0x0a65, 0x0a75, 0x0a80, 0x0a84, 0x0a84, 0x0a8c, 0x0a8c, 0x0a8e, 0x0a8e, 0x0a92, 0x0a92, 0x0aa9, 0x0aa9, 0x0ab1, 0x0ab1, 0x0ab4, 0x0ab4, 0x0aba, 0x0abb, 0x0ac6, 0x0ac6, 0x0aca, 0x0aca, 0x0ace, 0x0acf, 0x0ad1, 0x0adf, 0x0ae1, 0x0ae5, 0x0af0, 0x0b00, 0x0b04, 0x0b04, 0x0b0d, 0x0b0e, 0x0b11, 0x0b12, 0x0b29, 0x0b29, 0x0b31, 0x0b31, 0x0b34, 0x0b35, 0x0b3a, 0x0b3b, 0x0b44, 0x0b46, 0x0b49, 0x0b4a, 0x0b4e, 0x0b55, 0x0b58, 0x0b5b, 0x0b5e, 0x0b5e, 0x0b62, 0x0b65, 0x0b71, 0x0b81, 0x0b84, 0x0b84, 0x0b8b, 0x0b8d, 0x0b91, 0x0b91, 0x0b96, 0x0b98, 0x0b9b, 0x0b9b, 0x0b9d, 0x0b9d, 0x0ba0, 0x0ba2, 0x0ba5, 0x0ba7, 0x0bab, 0x0bad, 0x0bb6, 0x0bb6, 0x0bba, 0x0bbd, 0x0bc3, 0x0bc5, 0x0bc9, 0x0bc9, 0x0bce, 0x0bd6, 0x0bd8, 0x0be6, 0x0bf3, 0x0c00, 0x0c04, 0x0c04, 0x0c0d, 0x0c0d, 0x0c11, 0x0c11, 0x0c29, 0x0c29, 0x0c34, 0x0c34, 0x0c3a, 0x0c3d, 0x0c45, 0x0c45, 0x0c49, 0x0c49, 0x0c4e, 0x0c54, 0x0c57, 0x0c5f, 0x0c62, 0x0c65, 0x0c70, 0x0c81, 0x0c84, 0x0c84, 0x0c8d, 0x0c8d, 0x0c91, 0x0c91, 0x0ca9, 0x0ca9, 0x0cb4, 0x0cb4, 0x0cba, 0x0cbd, 0x0cc5, 0x0cc5, 0x0cc9, 0x0cc9, 0x0cce, 0x0cd4, 0x0cd7, 0x0cdd, 0x0cdf, 0x0cdf, 0x0ce2, 0x0ce5, 0x0cf0, 0x0d01, 0x0d04, 0x0d04, 0x0d0d, 0x0d0d, 0x0d11, 0x0d11, 0x0d29, 0x0d29, 0x0d3a, 0x0d3d, 0x0d44, 0x0d45, 0x0d49, 0x0d49, 0x0d4e, 0x0d56, 0x0d58, 0x0d5f, 0x0d62, 0x0d65, 0x0d70, 0x0d81, 0x0d84, 0x0d84, 0x0d97, 0x0d99, 0x0db2, 0x0db2, 0x0dbc, 0x0dbc, 0x0dbe, 0x0dbf, 0x0dc7, 0x0dc9, 0x0dcb, 0x0dce, 0x0dd5, 0x0dd5, 0x0dd7, 0x0dd7, 0x0de0, 0x0df1, 0x0df5, 0x0e00, 0x0e3b, 0x0e3e, 0x0e5c, 0x0e80, 0x0e83, 0x0e83, 0x0e85, 0x0e86, 0x0e89, 0x0e89, 0x0e8b, 0x0e8c, 0x0e8e, 0x0e93, 0x0e98, 0x0e98, 0x0ea0, 0x0ea0, 0x0ea4, 0x0ea4, 0x0ea6, 0x0ea6, 0x0ea8, 0x0ea9, 0x0eac, 0x0eac, 0x0eba, 0x0eba, 0x0ebe, 0x0ebf, 0x0ec5, 0x0ec5, 0x0ec7, 0x0ec7, 0x0ece, 0x0ecf, 0x0eda, 0x0edb, 0x0ede, 0x0eff, 0x0f48, 0x0f48, 0x0f6b, 0x0f70, 0x0f8c, 0x0f8f, 0x0f98, 0x0f98, 0x0fbd, 0x0fbd, 0x0fcd, 0x0fce, 0x0fd0, 0x0fff, 0x1022, 0x1022, 0x1028, 0x1028, 0x102b, 0x102b, 0x1033, 0x1035, 0x103a, 0x103f, 0x105a, 0x109f, 0x10c6, 0x10cf, 0x10f9, 0x10fa, 0x10fc, 0x10ff, 0x115a, 0x115e, 0x11a3, 0x11a7, 0x11fa, 0x11ff, 0x1207, 0x1207, 0x1247, 0x1247, 0x1249, 0x1249, 0x124e, 0x124f, 0x1257, 0x1257, 0x1259, 0x1259, 0x125e, 0x125f, 0x1287, 0x1287, 0x1289, 0x1289, 0x128e, 0x128f, 0x12af, 0x12af, 0x12b1, 0x12b1, 0x12b6, 0x12b7, 0x12bf, 0x12bf, 0x12c1, 0x12c1, 0x12c6, 0x12c7, 0x12cf, 0x12cf, 0x12d7, 0x12d7, 0x12ef, 0x12ef, 0x130f, 0x130f, 0x1311, 0x1311, 0x1316, 0x1317, 0x131f, 0x131f, 0x1347, 0x1347, 0x135b, 0x1360, 0x137d, 0x139f, 0x13f5, 0x1400, 0x1677, 0x167f, 0x169d, 0x169f, 0x16f1, 0x16ff, 0x170d, 0x170d, 0x1715, 0x171f, 0x1737, 0x173f, 0x1754, 0x175f, 0x176d, 0x176d, 0x1771, 0x1771, 0x1774, 0x177f, 0x17dd, 0x17df, 0x17ea, 0x17ff, 0x180f, 0x180f, 0x181a, 0x181f, 0x1878, 0x187f, 0x18aa, 0x1dff, 0x1e9c, 0x1e9f, 0x1efa, 0x1eff, 0x1f16, 0x1f17, 0x1f1e, 0x1f1f, 0x1f46, 0x1f47, 0x1f4e, 0x1f4f, 0x1f58, 0x1f58, 0x1f5a, 0x1f5a, 0x1f5c, 0x1f5c, 0x1f5e, 0x1f5e, 0x1f7e, 0x1f7f, 0x1fb5, 0x1fb5, 0x1fc5, 0x1fc5, 0x1fd4, 0x1fd5, 0x1fdc, 0x1fdc, 0x1ff0, 0x1ff1, 0x1ff5, 0x1ff5, 0x1fff, 0x1fff, 0x2053, 0x2056, 0x2058, 0x205e, 0x2064, 0x2069, 0x2072, 0x2073, 0x208f, 0x209f, 0x20b2, 0x20cf, 0x20eb, 0x20ff, 0x213b, 0x213c, 0x214c, 0x2152, 0x2184, 0x218f, 0x23cf, 0x23ff, 0x2427, 0x243f, 0x244b, 0x245f, 0x24ff, 0x24ff, 0x2614, 0x2615, 0x2618, 0x2618, 0x267e, 0x267f, 0x268a, 0x2700, 0x2705, 0x2705, 0x270a, 0x270b, 0x2728, 0x2728, 0x274c, 0x274c, 0x274e, 0x274e, 0x2753, 0x2755, 0x2757, 0x2757, 0x275f, 0x2760, 0x2795, 0x2797, 0x27b0, 0x27b0, 0x27bf, 0x27cf, 0x27ec, 0x27ef, 0x2b00, 0x2e7f, 0x2e9a, 0x2e9a, 0x2ef4, 0x2eff, 0x2fd6, 0x2fef, 0x2ffc, 0x2fff, 0x3040, 0x3040, 0x3097, 0x3098, 0x3100, 0x3104, 0x312d, 0x3130, 0x318f, 0x318f, 0x31b8, 0x31ef, 0x321d, 0x321f, 0x3244, 0x3250, 0x327c, 0x327e, 0x32cc, 0x32cf, 0x32ff, 0x32ff, 0x3377, 0x337a, 0x33de, 0x33df, 0x33ff, 0x33ff, 0x4db6, 0x4dff, 0x9fa6, 0x9fff, 0xa48d, 0xa48f, 0xa4c7, 0xabff, 0xd7a4, 0xd7ff, 0xfa2e, 0xfa2f, 0xfa6b, 0xfaff, 0xfb07, 0xfb12, 0xfb18, 0xfb1c, 0xfb37, 0xfb37, 0xfb3d, 0xfb3d, 0xfb3f, 0xfb3f, 0xfb42, 0xfb42, 0xfb45, 0xfb45, 0xfbb2, 0xfbd2, 0xfd40, 0xfd4f, 0xfd90, 0xfd91, 0xfdc8, 0xfdcf, 0xfdfd, 0xfdff, 0xfe10, 0xfe1f, 0xfe24, 0xfe2f, 0xfe47, 0xfe48, 0xfe53, 0xfe53, 0xfe67, 0xfe67, 0xfe6c, 0xfe6f, 0xfe75, 0xfe75, 0xfefd, 0xfefe, 0xff00, 0xff00, 0xffbf, 0xffc1, 0xffc8, 0xffc9, 0xffd0, 0xffd1, 0xffd8, 0xffd9, 0xffdd, 0xffdf, 0xffe7, 0xffe7, 0xffef, 0xfff8, 0x10000, 0x102ff, 0x1031f, 0x1031f, 0x10324, 0x1032f, 0x1034b, 0x103ff, 0x10426, 0x10427, 0x1044e, 0x1cfff, 0x1d0f6, 0x1d0ff, 0x1d127, 0x1d129, 0x1d1de, 0x1d3ff, 0x1d455, 0x1d455, 0x1d49d, 0x1d49d, 0x1d4a0, 0x1d4a1, 0x1d4a3, 0x1d4a4, 0x1d4a7, 0x1d4a8, 0x1d4ad, 0x1d4ad, 0x1d4ba, 0x1d4ba, 0x1d4bc, 0x1d4bc, 0x1d4c1, 0x1d4c1, 0x1d4c4, 0x1d4c4, 0x1d506, 0x1d506, 0x1d50b, 0x1d50c, 0x1d515, 0x1d515, 0x1d51d, 0x1d51d, 0x1d53a, 0x1d53a, 0x1d53f, 0x1d53f, 0x1d545, 0x1d545, 0x1d547, 0x1d549, 0x1d551, 0x1d551, 0x1d6a4, 0x1d6a7, 0x1d7ca, 0x1d7cd, 0x1d800, 0x1fffd, 0x2a6d7, 0x2f7ff, 0x2fa1e, 0x2fffd, 0x30000, 0x3fffd, 0x40000, 0x4fffd, 0x50000, 0x5fffd, 0x60000, 0x6fffd, 0x70000, 0x7fffd, 0x80000, 0x8fffd, 0x90000, 0x9fffd, 0xa0000, 0xafffd, 0xb0000, 0xbfffd, 0xc0000, 0xcfffd, 0xd0000, 0xdfffd, 0xe0000, 0xe0000, 0xe0002, 0xe001f, 0xe0080, 0xefffd];
440
- // prettier-ignore-end
441
-
442
460
  const isUnassignedCodePoint = character => inRange(character, unassigned_code_points);
443
-
444
- // prettier-ignore-start
445
- /**
446
- * B.1 Commonly mapped to nothing
447
- * @link https://tools.ietf.org/html/rfc3454#appendix-B.1
448
- */
449
461
  const commonly_mapped_to_nothing = [0x00ad, 0x00ad, 0x034f, 0x034f, 0x1806, 0x1806, 0x180b, 0x180b, 0x180c, 0x180c, 0x180d, 0x180d, 0x200b, 0x200b, 0x200c, 0x200c, 0x200d, 0x200d, 0x2060, 0x2060, 0xfe00, 0xfe00, 0xfe01, 0xfe01, 0xfe02, 0xfe02, 0xfe03, 0xfe03, 0xfe04, 0xfe04, 0xfe05, 0xfe05, 0xfe06, 0xfe06, 0xfe07, 0xfe07, 0xfe08, 0xfe08, 0xfe09, 0xfe09, 0xfe0a, 0xfe0a, 0xfe0b, 0xfe0b, 0xfe0c, 0xfe0c, 0xfe0d, 0xfe0d, 0xfe0e, 0xfe0e, 0xfe0f, 0xfe0f, 0xfeff, 0xfeff];
450
- // prettier-ignore-end
451
-
452
462
  const isCommonlyMappedToNothing = character => inRange(character, commonly_mapped_to_nothing);
453
-
454
- // prettier-ignore-start
455
- /**
456
- * C.1.2 Non-ASCII space characters
457
- * @link https://tools.ietf.org/html/rfc3454#appendix-C.1.2
458
- */
459
- 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 */];
460
- // prettier-ignore-end
461
-
463
+ const non_ASCII_space_characters = [0x00a0, 0x00a0, 0x1680, 0x1680, 0x2000, 0x2000, 0x2001, 0x2001, 0x2002, 0x2002, 0x2003, 0x2003, 0x2004, 0x2004, 0x2005, 0x2005, 0x2006, 0x2006, 0x2007, 0x2007, 0x2008, 0x2008, 0x2009, 0x2009, 0x200a, 0x200a, 0x200b, 0x200b, 0x202f, 0x202f, 0x205f, 0x205f, 0x3000, 0x3000];
462
464
  const isNonASCIISpaceCharacter = character => inRange(character, non_ASCII_space_characters);
463
-
464
- // prettier-ignore-start
465
- const non_ASCII_controls_characters = [
466
- /**
467
- * C.2.2 Non-ASCII control characters
468
- * @link https://tools.ietf.org/html/rfc3454#appendix-C.2.2
469
- */
470
- 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] */];
471
- const non_character_codepoints = [
472
- /**
473
- * C.4 Non-character code points
474
- * @link https://tools.ietf.org/html/rfc3454#appendix-C.4
475
- */
476
- 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] */];
477
-
478
- /**
479
- * 2.3. Prohibited Output
480
- */
481
- const prohibited_characters = [
482
- /**
483
- * C.2.1 ASCII control characters
484
- * @link https://tools.ietf.org/html/rfc3454#appendix-C.2.1
485
- */
486
- 0, 0x001f /* [CONTROL CHARACTERS] */, 0x007f, 0x007f /* DELETE */,
487
- /**
488
- * C.8 Change display properties or are deprecated
489
- * @link https://tools.ietf.org/html/rfc3454#appendix-C.8
490
- */
491
- 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 */,
492
- /**
493
- * C.7 Inappropriate for canonical representation
494
- * @link https://tools.ietf.org/html/rfc3454#appendix-C.7
495
- */
496
- 0x2ff0, 0x2ffb /* [IDEOGRAPHIC DESCRIPTION CHARACTERS] */,
497
- /**
498
- * C.5 Surrogate codes
499
- * @link https://tools.ietf.org/html/rfc3454#appendix-C.5
500
- */
501
- 0xd800, 0xdfff,
502
- /**
503
- * C.3 Private use
504
- * @link https://tools.ietf.org/html/rfc3454#appendix-C.3
505
- */
506
- 0xe000, 0xf8ff /* [PRIVATE USE, PLANE 0] */,
507
- /**
508
- * C.6 Inappropriate for plain text
509
- * @link https://tools.ietf.org/html/rfc3454#appendix-C.6
510
- */
511
- 0xfff9, 0xfff9 /* INTERLINEAR ANNOTATION ANCHOR */, 0xfffa, 0xfffa /* INTERLINEAR ANNOTATION SEPARATOR */, 0xfffb, 0xfffb /* INTERLINEAR ANNOTATION TERMINATOR */, 0xfffc, 0xfffc /* OBJECT REPLACEMENT CHARACTER */, 0xfffd, 0xfffd /* REPLACEMENT CHARACTER */,
512
- /**
513
- * C.9 Tagging characters
514
- * @link https://tools.ietf.org/html/rfc3454#appendix-C.9
515
- */
516
- 0xe0001, 0xe0001 /* LANGUAGE TAG */, 0xe0020, 0xe007f /* [TAGGING CHARACTERS] */,
517
- /**
518
- * C.3 Private use
519
- * @link https://tools.ietf.org/html/rfc3454#appendix-C.3
520
- */
521
-
522
- 0xf0000, 0xffffd /* [PRIVATE USE, PLANE 15] */, 0x100000, 0x10fffd /* [PRIVATE USE, PLANE 16] */];
523
- // prettier-ignore-end
524
-
465
+ const non_ASCII_controls_characters = [0x0080, 0x009f, 0x06dd, 0x06dd, 0x070f, 0x070f, 0x180e, 0x180e, 0x200c, 0x200c, 0x200d, 0x200d, 0x2028, 0x2028, 0x2029, 0x2029, 0x2060, 0x2060, 0x2061, 0x2061, 0x2062, 0x2062, 0x2063, 0x2063, 0x206a, 0x206f, 0xfeff, 0xfeff, 0xfff9, 0xfffc, 0x1d173, 0x1d17a];
466
+ const non_character_codepoints = [0xfdd0, 0xfdef, 0xfffe, 0xffff, 0x1fffe, 0x1ffff, 0x2fffe, 0x2ffff, 0x3fffe, 0x3ffff, 0x4fffe, 0x4ffff, 0x5fffe, 0x5ffff, 0x6fffe, 0x6ffff, 0x7fffe, 0x7ffff, 0x8fffe, 0x8ffff, 0x9fffe, 0x9ffff, 0xafffe, 0xaffff, 0xbfffe, 0xbffff, 0xcfffe, 0xcffff, 0xdfffe, 0xdffff, 0xefffe, 0xeffff, 0x10fffe, 0x10ffff];
467
+ const prohibited_characters = [0, 0x001f, 0x007f, 0x007f, 0x0340, 0x0340, 0x0341, 0x0341, 0x200e, 0x200e, 0x200f, 0x200f, 0x202a, 0x202a, 0x202b, 0x202b, 0x202c, 0x202c, 0x202d, 0x202d, 0x202e, 0x202e, 0x206a, 0x206a, 0x206b, 0x206b, 0x206c, 0x206c, 0x206d, 0x206d, 0x206e, 0x206e, 0x206f, 0x206f, 0x2ff0, 0x2ffb, 0xd800, 0xdfff, 0xe000, 0xf8ff, 0xfff9, 0xfff9, 0xfffa, 0xfffa, 0xfffb, 0xfffb, 0xfffc, 0xfffc, 0xfffd, 0xfffd, 0xe0001, 0xe0001, 0xe0020, 0xe007f, 0xf0000, 0xffffd, 0x100000, 0x10fffd];
525
468
  const isProhibitedCharacter = character => inRange(character, non_ASCII_space_characters) || inRange(character, prohibited_characters) || inRange(character, non_ASCII_controls_characters) || inRange(character, non_character_codepoints);
526
-
527
- // prettier-ignore-start
528
- /**
529
- * D.1 Characters with bidirectional property "R" or "AL"
530
- * @link https://tools.ietf.org/html/rfc3454#appendix-D.1
531
- */
532
469
  const bidirectional_r_al = [0x05be, 0x05be, 0x05c0, 0x05c0, 0x05c3, 0x05c3, 0x05d0, 0x05ea, 0x05f0, 0x05f4, 0x061b, 0x061b, 0x061f, 0x061f, 0x0621, 0x063a, 0x0640, 0x064a, 0x066d, 0x066f, 0x0671, 0x06d5, 0x06dd, 0x06dd, 0x06e5, 0x06e6, 0x06fa, 0x06fe, 0x0700, 0x070d, 0x0710, 0x0710, 0x0712, 0x072c, 0x0780, 0x07a5, 0x07b1, 0x07b1, 0x200f, 0x200f, 0xfb1d, 0xfb1d, 0xfb1f, 0xfb28, 0xfb2a, 0xfb36, 0xfb38, 0xfb3c, 0xfb3e, 0xfb3e, 0xfb40, 0xfb41, 0xfb43, 0xfb44, 0xfb46, 0xfbb1, 0xfbd3, 0xfd3d, 0xfd50, 0xfd8f, 0xfd92, 0xfdc7, 0xfdf0, 0xfdfc, 0xfe70, 0xfe74, 0xfe76, 0xfefc];
533
- // prettier-ignore-end
534
-
535
470
  const isBidirectionalRAL = character => inRange(character, bidirectional_r_al);
536
-
537
- // prettier-ignore-start
538
- /**
539
- * D.2 Characters with bidirectional property "L"
540
- * @link https://tools.ietf.org/html/rfc3454#appendix-D.2
541
- */
542
471
  const bidirectional_l = [0x0041, 0x005a, 0x0061, 0x007a, 0x00aa, 0x00aa, 0x00b5, 0x00b5, 0x00ba, 0x00ba, 0x00c0, 0x00d6, 0x00d8, 0x00f6, 0x00f8, 0x0220, 0x0222, 0x0233, 0x0250, 0x02ad, 0x02b0, 0x02b8, 0x02bb, 0x02c1, 0x02d0, 0x02d1, 0x02e0, 0x02e4, 0x02ee, 0x02ee, 0x037a, 0x037a, 0x0386, 0x0386, 0x0388, 0x038a, 0x038c, 0x038c, 0x038e, 0x03a1, 0x03a3, 0x03ce, 0x03d0, 0x03f5, 0x0400, 0x0482, 0x048a, 0x04ce, 0x04d0, 0x04f5, 0x04f8, 0x04f9, 0x0500, 0x050f, 0x0531, 0x0556, 0x0559, 0x055f, 0x0561, 0x0587, 0x0589, 0x0589, 0x0903, 0x0903, 0x0905, 0x0939, 0x093d, 0x0940, 0x0949, 0x094c, 0x0950, 0x0950, 0x0958, 0x0961, 0x0964, 0x0970, 0x0982, 0x0983, 0x0985, 0x098c, 0x098f, 0x0990, 0x0993, 0x09a8, 0x09aa, 0x09b0, 0x09b2, 0x09b2, 0x09b6, 0x09b9, 0x09be, 0x09c0, 0x09c7, 0x09c8, 0x09cb, 0x09cc, 0x09d7, 0x09d7, 0x09dc, 0x09dd, 0x09df, 0x09e1, 0x09e6, 0x09f1, 0x09f4, 0x09fa, 0x0a05, 0x0a0a, 0x0a0f, 0x0a10, 0x0a13, 0x0a28, 0x0a2a, 0x0a30, 0x0a32, 0x0a33, 0x0a35, 0x0a36, 0x0a38, 0x0a39, 0x0a3e, 0x0a40, 0x0a59, 0x0a5c, 0x0a5e, 0x0a5e, 0x0a66, 0x0a6f, 0x0a72, 0x0a74, 0x0a83, 0x0a83, 0x0a85, 0x0a8b, 0x0a8d, 0x0a8d, 0x0a8f, 0x0a91, 0x0a93, 0x0aa8, 0x0aaa, 0x0ab0, 0x0ab2, 0x0ab3, 0x0ab5, 0x0ab9, 0x0abd, 0x0ac0, 0x0ac9, 0x0ac9, 0x0acb, 0x0acc, 0x0ad0, 0x0ad0, 0x0ae0, 0x0ae0, 0x0ae6, 0x0aef, 0x0b02, 0x0b03, 0x0b05, 0x0b0c, 0x0b0f, 0x0b10, 0x0b13, 0x0b28, 0x0b2a, 0x0b30, 0x0b32, 0x0b33, 0x0b36, 0x0b39, 0x0b3d, 0x0b3e, 0x0b40, 0x0b40, 0x0b47, 0x0b48, 0x0b4b, 0x0b4c, 0x0b57, 0x0b57, 0x0b5c, 0x0b5d, 0x0b5f, 0x0b61, 0x0b66, 0x0b70, 0x0b83, 0x0b83, 0x0b85, 0x0b8a, 0x0b8e, 0x0b90, 0x0b92, 0x0b95, 0x0b99, 0x0b9a, 0x0b9c, 0x0b9c, 0x0b9e, 0x0b9f, 0x0ba3, 0x0ba4, 0x0ba8, 0x0baa, 0x0bae, 0x0bb5, 0x0bb7, 0x0bb9, 0x0bbe, 0x0bbf, 0x0bc1, 0x0bc2, 0x0bc6, 0x0bc8, 0x0bca, 0x0bcc, 0x0bd7, 0x0bd7, 0x0be7, 0x0bf2, 0x0c01, 0x0c03, 0x0c05, 0x0c0c, 0x0c0e, 0x0c10, 0x0c12, 0x0c28, 0x0c2a, 0x0c33, 0x0c35, 0x0c39, 0x0c41, 0x0c44, 0x0c60, 0x0c61, 0x0c66, 0x0c6f, 0x0c82, 0x0c83, 0x0c85, 0x0c8c, 0x0c8e, 0x0c90, 0x0c92, 0x0ca8, 0x0caa, 0x0cb3, 0x0cb5, 0x0cb9, 0x0cbe, 0x0cbe, 0x0cc0, 0x0cc4, 0x0cc7, 0x0cc8, 0x0cca, 0x0ccb, 0x0cd5, 0x0cd6, 0x0cde, 0x0cde, 0x0ce0, 0x0ce1, 0x0ce6, 0x0cef, 0x0d02, 0x0d03, 0x0d05, 0x0d0c, 0x0d0e, 0x0d10, 0x0d12, 0x0d28, 0x0d2a, 0x0d39, 0x0d3e, 0x0d40, 0x0d46, 0x0d48, 0x0d4a, 0x0d4c, 0x0d57, 0x0d57, 0x0d60, 0x0d61, 0x0d66, 0x0d6f, 0x0d82, 0x0d83, 0x0d85, 0x0d96, 0x0d9a, 0x0db1, 0x0db3, 0x0dbb, 0x0dbd, 0x0dbd, 0x0dc0, 0x0dc6, 0x0dcf, 0x0dd1, 0x0dd8, 0x0ddf, 0x0df2, 0x0df4, 0x0e01, 0x0e30, 0x0e32, 0x0e33, 0x0e40, 0x0e46, 0x0e4f, 0x0e5b, 0x0e81, 0x0e82, 0x0e84, 0x0e84, 0x0e87, 0x0e88, 0x0e8a, 0x0e8a, 0x0e8d, 0x0e8d, 0x0e94, 0x0e97, 0x0e99, 0x0e9f, 0x0ea1, 0x0ea3, 0x0ea5, 0x0ea5, 0x0ea7, 0x0ea7, 0x0eaa, 0x0eab, 0x0ead, 0x0eb0, 0x0eb2, 0x0eb3, 0x0ebd, 0x0ebd, 0x0ec0, 0x0ec4, 0x0ec6, 0x0ec6, 0x0ed0, 0x0ed9, 0x0edc, 0x0edd, 0x0f00, 0x0f17, 0x0f1a, 0x0f34, 0x0f36, 0x0f36, 0x0f38, 0x0f38, 0x0f3e, 0x0f47, 0x0f49, 0x0f6a, 0x0f7f, 0x0f7f, 0x0f85, 0x0f85, 0x0f88, 0x0f8b, 0x0fbe, 0x0fc5, 0x0fc7, 0x0fcc, 0x0fcf, 0x0fcf, 0x1000, 0x1021, 0x1023, 0x1027, 0x1029, 0x102a, 0x102c, 0x102c, 0x1031, 0x1031, 0x1038, 0x1038, 0x1040, 0x1057, 0x10a0, 0x10c5, 0x10d0, 0x10f8, 0x10fb, 0x10fb, 0x1100, 0x1159, 0x115f, 0x11a2, 0x11a8, 0x11f9, 0x1200, 0x1206, 0x1208, 0x1246, 0x1248, 0x1248, 0x124a, 0x124d, 0x1250, 0x1256, 0x1258, 0x1258, 0x125a, 0x125d, 0x1260, 0x1286, 0x1288, 0x1288, 0x128a, 0x128d, 0x1290, 0x12ae, 0x12b0, 0x12b0, 0x12b2, 0x12b5, 0x12b8, 0x12be, 0x12c0, 0x12c0, 0x12c2, 0x12c5, 0x12c8, 0x12ce, 0x12d0, 0x12d6, 0x12d8, 0x12ee, 0x12f0, 0x130e, 0x1310, 0x1310, 0x1312, 0x1315, 0x1318, 0x131e, 0x1320, 0x1346, 0x1348, 0x135a, 0x1361, 0x137c, 0x13a0, 0x13f4, 0x1401, 0x1676, 0x1681, 0x169a, 0x16a0, 0x16f0, 0x1700, 0x170c, 0x170e, 0x1711, 0x1720, 0x1731, 0x1735, 0x1736, 0x1740, 0x1751, 0x1760, 0x176c, 0x176e, 0x1770, 0x1780, 0x17b6, 0x17be, 0x17c5, 0x17c7, 0x17c8, 0x17d4, 0x17da, 0x17dc, 0x17dc, 0x17e0, 0x17e9, 0x1810, 0x1819, 0x1820, 0x1877, 0x1880, 0x18a8, 0x1e00, 0x1e9b, 0x1ea0, 0x1ef9, 0x1f00, 0x1f15, 0x1f18, 0x1f1d, 0x1f20, 0x1f45, 0x1f48, 0x1f4d, 0x1f50, 0x1f57, 0x1f59, 0x1f59, 0x1f5b, 0x1f5b, 0x1f5d, 0x1f5d, 0x1f5f, 0x1f7d, 0x1f80, 0x1fb4, 0x1fb6, 0x1fbc, 0x1fbe, 0x1fbe, 0x1fc2, 0x1fc4, 0x1fc6, 0x1fcc, 0x1fd0, 0x1fd3, 0x1fd6, 0x1fdb, 0x1fe0, 0x1fec, 0x1ff2, 0x1ff4, 0x1ff6, 0x1ffc, 0x200e, 0x200e, 0x2071, 0x2071, 0x207f, 0x207f, 0x2102, 0x2102, 0x2107, 0x2107, 0x210a, 0x2113, 0x2115, 0x2115, 0x2119, 0x211d, 0x2124, 0x2124, 0x2126, 0x2126, 0x2128, 0x2128, 0x212a, 0x212d, 0x212f, 0x2131, 0x2133, 0x2139, 0x213d, 0x213f, 0x2145, 0x2149, 0x2160, 0x2183, 0x2336, 0x237a, 0x2395, 0x2395, 0x249c, 0x24e9, 0x3005, 0x3007, 0x3021, 0x3029, 0x3031, 0x3035, 0x3038, 0x303c, 0x3041, 0x3096, 0x309d, 0x309f, 0x30a1, 0x30fa, 0x30fc, 0x30ff, 0x3105, 0x312c, 0x3131, 0x318e, 0x3190, 0x31b7, 0x31f0, 0x321c, 0x3220, 0x3243, 0x3260, 0x327b, 0x327f, 0x32b0, 0x32c0, 0x32cb, 0x32d0, 0x32fe, 0x3300, 0x3376, 0x337b, 0x33dd, 0x33e0, 0x33fe, 0x3400, 0x4db5, 0x4e00, 0x9fa5, 0xa000, 0xa48c, 0xac00, 0xd7a3, 0xd800, 0xfa2d, 0xfa30, 0xfa6a, 0xfb00, 0xfb06, 0xfb13, 0xfb17, 0xff21, 0xff3a, 0xff41, 0xff5a, 0xff66, 0xffbe, 0xffc2, 0xffc7, 0xffca, 0xffcf, 0xffd2, 0xffd7, 0xffda, 0xffdc, 0x10300, 0x1031e, 0x10320, 0x10323, 0x10330, 0x1034a, 0x10400, 0x10425, 0x10428, 0x1044d, 0x1d000, 0x1d0f5, 0x1d100, 0x1d126, 0x1d12a, 0x1d166, 0x1d16a, 0x1d172, 0x1d183, 0x1d184, 0x1d18c, 0x1d1a9, 0x1d1ae, 0x1d1dd, 0x1d400, 0x1d454, 0x1d456, 0x1d49c, 0x1d49e, 0x1d49f, 0x1d4a2, 0x1d4a2, 0x1d4a5, 0x1d4a6, 0x1d4a9, 0x1d4ac, 0x1d4ae, 0x1d4b9, 0x1d4bb, 0x1d4bb, 0x1d4bd, 0x1d4c0, 0x1d4c2, 0x1d4c3, 0x1d4c5, 0x1d505, 0x1d507, 0x1d50a, 0x1d50d, 0x1d514, 0x1d516, 0x1d51c, 0x1d51e, 0x1d539, 0x1d53b, 0x1d53e, 0x1d540, 0x1d544, 0x1d546, 0x1d546, 0x1d54a, 0x1d550, 0x1d552, 0x1d6a3, 0x1d6a8, 0x1d7c9, 0x20000, 0x2a6d6, 0x2f800, 0x2fa1d, 0xf0000, 0xffffd, 0x100000, 0x10fffd];
543
- // prettier-ignore-end
544
-
545
472
  const isBidirectionalL = character => inRange(character, bidirectional_l);
546
473
 
547
- // 2.1. Mapping
548
-
549
- /**
550
- * non-ASCII space characters [StringPrep, C.1.2] that can be
551
- * mapped to SPACE (U+0020)
552
- */
553
474
  const mapping2space = isNonASCIISpaceCharacter;
554
-
555
- /**
556
- * the "commonly mapped to nothing" characters [StringPrep, B.1]
557
- * that can be mapped to nothing.
558
- */
559
475
  const mapping2nothing = isCommonlyMappedToNothing;
560
-
561
- // utils
562
476
  const getCodePoint = character => character.codePointAt(0);
563
477
  const first = x => x[0];
564
478
  const last = x => x[x.length - 1];
565
-
566
- /**
567
- * Convert provided string into an array of Unicode Code Points.
568
- * Based on https://stackoverflow.com/a/21409165/1556249
569
- * and https://www.npmjs.com/package/code-point-at.
570
- * @param {string} input
571
- * @returns {number[]}
572
- */
573
479
  function toCodePoints(input) {
574
480
  const codepoints = [];
575
481
  const size = input.length;
@@ -587,14 +493,6 @@ function toCodePoints(input) {
587
493
  }
588
494
  return codepoints;
589
495
  }
590
-
591
- /**
592
- * SASLprep.
593
- * @param {string} input
594
- * @param {Object} opts
595
- * @param {boolean} opts.allowUnassigned
596
- * @returns {string}
597
- */
598
496
  function saslprep(input, opts = {}) {
599
497
  if (typeof input !== 'string') {
600
498
  throw new TypeError('Expected string.');
@@ -602,49 +500,24 @@ function saslprep(input, opts = {}) {
602
500
  if (input.length === 0) {
603
501
  return '';
604
502
  }
605
-
606
- // 1. Map
607
- const mapped_input = toCodePoints(input)
608
- // 1.1 mapping to space
609
- .map(character => mapping2space(character) ? 0x20 : character)
610
- // 1.2 mapping to nothing
611
- .filter(character => !mapping2nothing(character));
612
-
613
- // 2. Normalize
503
+ const mapped_input = toCodePoints(input).map(character => mapping2space(character) ? 0x20 : character).filter(character => !mapping2nothing(character));
614
504
  const normalized_input = String.fromCodePoint.apply(null, mapped_input).normalize('NFKC');
615
505
  const normalized_map = toCodePoints(normalized_input);
616
-
617
- // 3. Prohibit
618
506
  const hasProhibited = normalized_map.some(isProhibitedCharacter);
619
507
  if (hasProhibited) {
620
508
  throw new Error('Prohibited character, see https://tools.ietf.org/html/rfc4013#section-2.3');
621
509
  }
622
-
623
- // Unassigned Code Points
624
510
  if (opts.allowUnassigned !== true) {
625
511
  const hasUnassigned = normalized_map.some(isUnassignedCodePoint);
626
512
  if (hasUnassigned) {
627
513
  throw new Error('Unassigned code point, see https://tools.ietf.org/html/rfc4013#section-2.5');
628
514
  }
629
515
  }
630
-
631
- // 4. check bidi
632
-
633
516
  const hasBidiRAL = normalized_map.some(isBidirectionalRAL);
634
517
  const hasBidiL = normalized_map.some(isBidirectionalL);
635
-
636
- // 4.1 If a string contains any RandALCat character, the string MUST NOT
637
- // contain any LCat character.
638
518
  if (hasBidiRAL && hasBidiL) {
639
519
  throw new Error('String must not contain RandALCat and LCat at the same time,' + ' see https://tools.ietf.org/html/rfc3454#section-6');
640
520
  }
641
-
642
- /**
643
- * 4.2 If a string contains any RandALCat character, a RandALCat
644
- * character MUST be the first character of the string, and a
645
- * RandALCat character MUST be the last character of the string.
646
- */
647
-
648
521
  const isFirstBidiRAL = isBidirectionalRAL(getCodePoint(first(normalized_input)));
649
522
  const isLastBidiRAL = isBidirectionalRAL(getCodePoint(last(normalized_input)));
650
523
  if (hasBidiRAL && !(isFirstBidiRAL && isLastBidiRAL)) {
@@ -653,15 +526,10 @@ function saslprep(input, opts = {}) {
653
526
  return normalized_input;
654
527
  }
655
528
 
656
- /*
657
- PDFSecurity - represents PDF security settings
658
- By Yang Liu <hi@zesik.com>
659
- */
660
529
  class PDFSecurity {
661
530
  static generateFileID(info = {}) {
662
531
  let infoStr = `${info.CreationDate.getTime()}\n`;
663
532
  for (let key in info) {
664
- // eslint-disable-next-line no-prototype-builtins
665
533
  if (!info.hasOwnProperty(key)) {
666
534
  continue;
667
535
  }
@@ -992,9 +860,9 @@ function wordArrayToBuffer(wordArray) {
992
860
  const PASSWORD_PADDING = [0x28, 0xbf, 0x4e, 0x5e, 0x4e, 0x75, 0x8a, 0x41, 0x64, 0x00, 0x4e, 0x56, 0xff, 0xfa, 0x01, 0x08, 0x2e, 0x2e, 0x00, 0xb6, 0xd0, 0x68, 0x3e, 0x80, 0x2f, 0x0c, 0xa9, 0xfe, 0x64, 0x53, 0x69, 0x7a];
993
861
 
994
862
  const {
995
- number
863
+ number: number$2
996
864
  } = PDFObject;
997
- class PDFGradient {
865
+ class PDFGradient$1 {
998
866
  constructor(doc) {
999
867
  this.doc = doc;
1000
868
  this.stops = [];
@@ -1035,8 +903,6 @@ class PDFGradient {
1035
903
  }
1036
904
  this.embedded = true;
1037
905
  this.matrix = m;
1038
-
1039
- // if the last stop comes before 100%, add a copy at 100%
1040
906
  const last = this.stops[stopsLength - 1];
1041
907
  if (last[0] < 1) {
1042
908
  this.stops.push([1, last[1], last[2]]);
@@ -1059,14 +925,11 @@ class PDFGradient {
1059
925
  stops.push(fn);
1060
926
  fn.end();
1061
927
  }
1062
-
1063
- // if there are only two stops, we don't need a stitching function
1064
928
  if (stopsLength === 1) {
1065
929
  fn = stops[0];
1066
930
  } else {
1067
931
  fn = this.doc.ref({
1068
932
  FunctionType: 3,
1069
- // stitching function
1070
933
  Domain: [0, 1],
1071
934
  Functions: stops,
1072
935
  Bounds: bounds,
@@ -1081,7 +944,7 @@ class PDFGradient {
1081
944
  Type: 'Pattern',
1082
945
  PatternType: 2,
1083
946
  Shading: shader,
1084
- Matrix: this.matrix.map(number)
947
+ Matrix: this.matrix.map(number$2)
1085
948
  });
1086
949
  pattern.end();
1087
950
  if (this.stops.some(stop => stop[2] < 1)) {
@@ -1147,7 +1010,6 @@ class PDFGradient {
1147
1010
  return pattern;
1148
1011
  }
1149
1012
  apply(stroke) {
1150
- // apply gradient transform to existing document ctm
1151
1013
  const [m0, m1, m2, m3, m4, m5] = this.doc._ctm;
1152
1014
  const [m11, m12, m21, m22, dx, dy] = this.transform;
1153
1015
  const m = [m0 * m11 + m2 * m12, m1 * m11 + m3 * m12, m0 * m21 + m2 * m22, m1 * m21 + m3 * m22, m0 * dx + m2 * dy + m4, m1 * dx + m3 * dy + m5];
@@ -1159,7 +1021,7 @@ class PDFGradient {
1159
1021
  return this.doc.addContent(`/${this.id} ${op}`);
1160
1022
  }
1161
1023
  }
1162
- class PDFLinearGradient extends PDFGradient {
1024
+ class PDFLinearGradient$1 extends PDFGradient$1 {
1163
1025
  constructor(doc, x1, y1, x2, y2) {
1164
1026
  super(doc);
1165
1027
  this.x1 = x1;
@@ -1177,10 +1039,10 @@ class PDFLinearGradient extends PDFGradient {
1177
1039
  });
1178
1040
  }
1179
1041
  opacityGradient() {
1180
- return new PDFLinearGradient(this.doc, this.x1, this.y1, this.x2, this.y2);
1042
+ return new PDFLinearGradient$1(this.doc, this.x1, this.y1, this.x2, this.y2);
1181
1043
  }
1182
1044
  }
1183
- class PDFRadialGradient extends PDFGradient {
1045
+ class PDFRadialGradient$1 extends PDFGradient$1 {
1184
1046
  constructor(doc, x1, y1, r1, x2, y2, r2) {
1185
1047
  super(doc);
1186
1048
  this.doc = doc;
@@ -1201,21 +1063,17 @@ class PDFRadialGradient extends PDFGradient {
1201
1063
  });
1202
1064
  }
1203
1065
  opacityGradient() {
1204
- return new PDFRadialGradient(this.doc, this.x1, this.y1, this.r1, this.x2, this.y2, this.r2);
1066
+ return new PDFRadialGradient$1(this.doc, this.x1, this.y1, this.r1, this.x2, this.y2, this.r2);
1205
1067
  }
1206
1068
  }
1207
1069
  var Gradient = {
1208
- PDFGradient,
1209
- PDFLinearGradient,
1210
- PDFRadialGradient
1070
+ PDFGradient: PDFGradient$1,
1071
+ PDFLinearGradient: PDFLinearGradient$1,
1072
+ PDFRadialGradient: PDFRadialGradient$1
1211
1073
  };
1212
1074
 
1213
- /*
1214
- PDF tiling pattern support. Uncolored only.
1215
- */
1216
-
1217
1075
  const underlyingColorSpaces = ['DeviceCMYK', 'DeviceRGB'];
1218
- class PDFTilingPattern {
1076
+ class PDFTilingPattern$1 {
1219
1077
  constructor(doc, bBox, xStep, yStep, stream) {
1220
1078
  this.doc = doc;
1221
1079
  this.bBox = bBox;
@@ -1224,23 +1082,16 @@ class PDFTilingPattern {
1224
1082
  this.stream = stream;
1225
1083
  }
1226
1084
  createPattern() {
1227
- // no resources needed for our current usage
1228
- // required entry
1229
1085
  const resources = this.doc.ref();
1230
1086
  resources.end();
1231
- // apply default transform matrix (flipped in the default doc._ctm)
1232
- // see document.js & gradient.js
1233
1087
  const [m0, m1, m2, m3, m4, m5] = this.doc._ctm;
1234
1088
  const [m11, m12, m21, m22, dx, dy] = [1, 0, 0, 1, 0, 0];
1235
1089
  const m = [m0 * m11 + m2 * m12, m1 * m11 + m3 * m12, m0 * m21 + m2 * m22, m1 * m21 + m3 * m22, m0 * dx + m2 * dy + m4, m1 * dx + m3 * dy + m5];
1236
1090
  const pattern = this.doc.ref({
1237
1091
  Type: 'Pattern',
1238
1092
  PatternType: 1,
1239
- // tiling
1240
1093
  PaintType: 2,
1241
- // 1-colored, 2-uncolored
1242
1094
  TilingType: 2,
1243
- // 2-no distortion
1244
1095
  BBox: this.bBox,
1245
1096
  XStep: this.xStep,
1246
1097
  YStep: this.yStep,
@@ -1251,8 +1102,6 @@ class PDFTilingPattern {
1251
1102
  return pattern;
1252
1103
  }
1253
1104
  embedPatternColorSpaces() {
1254
- // map each pattern to an underlying color space
1255
- // and embed on each page
1256
1105
  underlyingColorSpaces.forEach(csName => {
1257
1106
  const csId = this.getPatternColorSpaceId(csName);
1258
1107
  if (this.doc.page.colorSpaces[csId]) return;
@@ -1270,47 +1119,40 @@ class PDFTilingPattern {
1270
1119
  this.id = 'P' + this.doc._patternCount;
1271
1120
  this.pattern = this.createPattern();
1272
1121
  }
1273
-
1274
- // patterns are embedded in each page
1275
1122
  if (!this.doc.page.patterns[this.id]) {
1276
1123
  this.doc.page.patterns[this.id] = this.pattern;
1277
1124
  }
1278
1125
  }
1279
1126
  apply(stroke, patternColor) {
1280
- // do any embedding/creating that might be needed
1281
1127
  this.embedPatternColorSpaces();
1282
1128
  this.embed();
1283
1129
  const normalizedColor = this.doc._normalizeColor(patternColor);
1284
1130
  if (!normalizedColor) throw Error(`invalid pattern color. (value: ${patternColor})`);
1285
-
1286
- // select one of the pattern color spaces
1287
1131
  const csId = this.getPatternColorSpaceId(this.doc._getColorSpace(normalizedColor));
1288
1132
  this.doc._setColorSpace(csId, stroke);
1289
-
1290
- // stroke/fill using the pattern and color (in the above underlying color space)
1291
1133
  const op = stroke ? 'SCN' : 'scn';
1292
1134
  return this.doc.addContent(`${normalizedColor.join(' ')} /${this.id} ${op}`);
1293
1135
  }
1294
1136
  }
1295
1137
  var pattern = {
1296
- PDFTilingPattern
1138
+ PDFTilingPattern: PDFTilingPattern$1
1297
1139
  };
1298
1140
 
1299
1141
  const {
1300
- PDFGradient: PDFGradient$1,
1301
- PDFLinearGradient: PDFLinearGradient$1,
1302
- PDFRadialGradient: PDFRadialGradient$1
1142
+ PDFGradient,
1143
+ PDFLinearGradient,
1144
+ PDFRadialGradient
1303
1145
  } = Gradient;
1304
1146
  const {
1305
- PDFTilingPattern: PDFTilingPattern$1
1147
+ PDFTilingPattern
1306
1148
  } = pattern;
1307
1149
  var ColorMixin = {
1308
1150
  initColor() {
1309
- // The opacity dictionaries
1151
+ this.spotColors = {};
1310
1152
  this._opacityRegistry = {};
1311
1153
  this._opacityCount = 0;
1312
1154
  this._patternCount = 0;
1313
- return this._gradCount = 0;
1155
+ this._gradCount = 0;
1314
1156
  },
1315
1157
  _normalizeColor(color) {
1316
1158
  if (typeof color === 'string') {
@@ -1322,13 +1164,13 @@ var ColorMixin = {
1322
1164
  color = [hex >> 16, hex >> 8 & 0xff, hex & 0xff];
1323
1165
  } else if (namedColors[color]) {
1324
1166
  color = namedColors[color];
1167
+ } else if (this.spotColors[color]) {
1168
+ return this.spotColors[color];
1325
1169
  }
1326
1170
  }
1327
1171
  if (Array.isArray(color)) {
1328
- // RGB
1329
1172
  if (color.length === 3) {
1330
1173
  color = color.map(part => part / 255);
1331
- // CMYK
1332
1174
  } else if (color.length === 4) {
1333
1175
  color = color.map(part => part / 100);
1334
1176
  }
@@ -1337,15 +1179,13 @@ var ColorMixin = {
1337
1179
  return null;
1338
1180
  },
1339
1181
  _setColor(color, stroke) {
1340
- if (color instanceof PDFGradient$1) {
1182
+ if (color instanceof PDFGradient) {
1341
1183
  color.apply(stroke);
1342
1184
  return true;
1343
- // see if tiling pattern, decode & apply it it
1344
- } else if (Array.isArray(color) && color[0] instanceof PDFTilingPattern$1) {
1185
+ } else if (Array.isArray(color) && color[0] instanceof PDFTilingPattern) {
1345
1186
  color[0].apply(stroke, color[1]);
1346
1187
  return true;
1347
1188
  }
1348
- // any other case should be a normal color and not a pattern
1349
1189
  return this._setColorCore(color, stroke);
1350
1190
  },
1351
1191
  _setColorCore(color, stroke) {
@@ -1356,8 +1196,12 @@ var ColorMixin = {
1356
1196
  const op = stroke ? 'SCN' : 'scn';
1357
1197
  const space = this._getColorSpace(color);
1358
1198
  this._setColorSpace(space, stroke);
1359
- color = color.join(' ');
1360
- this.addContent(`${color} ${op}`);
1199
+ if (color instanceof SpotColor) {
1200
+ this.page.colorSpaces[color.id] = color.ref;
1201
+ this.addContent(`1 ${op}`);
1202
+ } else {
1203
+ this.addContent(`${color.join(' ')} ${op}`);
1204
+ }
1361
1205
  return true;
1362
1206
  },
1363
1207
  _setColorSpace(space, stroke) {
@@ -1365,6 +1209,9 @@ var ColorMixin = {
1365
1209
  return this.addContent(`/${space} ${op}`);
1366
1210
  },
1367
1211
  _getColorSpace(color) {
1212
+ if (color instanceof SpotColor) {
1213
+ return color.id;
1214
+ }
1368
1215
  return color.length === 4 ? 'DeviceCMYK' : 'DeviceRGB';
1369
1216
  },
1370
1217
  fillColor(color, opacity) {
@@ -1372,9 +1219,6 @@ var ColorMixin = {
1372
1219
  if (set) {
1373
1220
  this.fillOpacity(opacity);
1374
1221
  }
1375
-
1376
- // save this for text wrapper, which needs to reset
1377
- // the fill color on new pages
1378
1222
  this._fillColor = [color, opacity];
1379
1223
  return this;
1380
1224
  },
@@ -1431,13 +1275,18 @@ var ColorMixin = {
1431
1275
  return this.addContent(`/${name} gs`);
1432
1276
  },
1433
1277
  linearGradient(x1, y1, x2, y2) {
1434
- return new PDFLinearGradient$1(this, x1, y1, x2, y2);
1278
+ return new PDFLinearGradient(this, x1, y1, x2, y2);
1435
1279
  },
1436
1280
  radialGradient(x1, y1, r1, x2, y2, r2) {
1437
- return new PDFRadialGradient$1(this, x1, y1, r1, x2, y2, r2);
1281
+ return new PDFRadialGradient(this, x1, y1, r1, x2, y2, r2);
1438
1282
  },
1439
1283
  pattern(bbox, xStep, yStep, stream) {
1440
- return new PDFTilingPattern$1(this, bbox, xStep, yStep, stream);
1284
+ return new PDFTilingPattern(this, bbox, xStep, yStep, stream);
1285
+ },
1286
+ addSpotColor(name, C, M, Y, K) {
1287
+ const color = new SpotColor(this, name, C, M, Y, K);
1288
+ this.spotColors[name] = color;
1289
+ return this;
1441
1290
  }
1442
1291
  };
1443
1292
  var namedColors = {
@@ -1625,7 +1474,6 @@ const parse = function (path) {
1625
1474
  if (parameters[c] != null) {
1626
1475
  params = parameters[c];
1627
1476
  if (cmd) {
1628
- // save existing command
1629
1477
  if (curArg.length > 0) {
1630
1478
  args[args.length] = +curArg;
1631
1479
  }
@@ -1643,14 +1491,11 @@ const parse = function (path) {
1643
1491
  continue;
1644
1492
  }
1645
1493
  if (args.length === params) {
1646
- // handle reused commands
1647
1494
  ret[ret.length] = {
1648
1495
  cmd,
1649
1496
  args
1650
1497
  };
1651
1498
  args = [+curArg];
1652
-
1653
- // handle assumed commands
1654
1499
  if (cmd === 'M') {
1655
1500
  cmd = 'L';
1656
1501
  }
@@ -1661,8 +1506,6 @@ const parse = function (path) {
1661
1506
  args[args.length] = +curArg;
1662
1507
  }
1663
1508
  foundDecimal = c === '.';
1664
-
1665
- // fix for negative numbers or repeated decimals with no delimeter between commands
1666
1509
  curArg = ['-', '.'].includes(c) ? c : '';
1667
1510
  } else {
1668
1511
  curArg += c;
@@ -1671,18 +1514,13 @@ const parse = function (path) {
1671
1514
  }
1672
1515
  }
1673
1516
  }
1674
-
1675
- // add the last command
1676
1517
  if (curArg.length > 0) {
1677
1518
  if (args.length === params) {
1678
- // handle reused commands
1679
1519
  ret[ret.length] = {
1680
1520
  cmd,
1681
1521
  args
1682
1522
  };
1683
1523
  args = [+curArg];
1684
-
1685
- // handle assumed commands
1686
1524
  if (cmd === 'M') {
1687
1525
  cmd = 'L';
1688
1526
  }
@@ -1700,10 +1538,7 @@ const parse = function (path) {
1700
1538
  return ret;
1701
1539
  };
1702
1540
  const apply = function (commands, doc) {
1703
- // current point, control point, and subpath starting point
1704
1541
  cx = cy = px = py = sx = sy = 0;
1705
-
1706
- // run the commands
1707
1542
  for (let i = 0; i < commands.length; i++) {
1708
1543
  const c = commands[i];
1709
1544
  if (typeof runners[c.cmd] === 'function') {
@@ -1867,8 +1702,6 @@ const solveArc = function (doc, x, y, coords) {
1867
1702
  doc.bezierCurveTo(...bez);
1868
1703
  }
1869
1704
  };
1870
-
1871
- // from Inkscape svgtopdf, thanks!
1872
1705
  const arcToSegments = function (x, y, rx, ry, large, sweep, rotateX, ox, oy) {
1873
1706
  const th = rotateX * (Math.PI / 180);
1874
1707
  const sin_th = Math.sin(th);
@@ -1944,18 +1777,14 @@ class SVGPath {
1944
1777
  const {
1945
1778
  number: number$1
1946
1779
  } = PDFObject;
1947
-
1948
- // This constant is used to approximate a symmetrical arc using a cubic
1949
- // Bezier curve.
1950
1780
  const KAPPA = 4.0 * ((Math.sqrt(2) - 1.0) / 3.0);
1951
1781
  var VectorMixin = {
1952
1782
  initVector() {
1953
- this._ctm = [1, 0, 0, 1, 0, 0]; // current transformation matrix
1954
- return this._ctmStack = [];
1783
+ this._ctm = [1, 0, 0, 1, 0, 0];
1784
+ this._ctmStack = [];
1955
1785
  },
1956
1786
  save() {
1957
1787
  this._ctmStack.push(this._ctm.slice());
1958
- // TODO: save/restore colorspace and styles so not setting it unnessesarily all the time?
1959
1788
  return this.addContent('q');
1960
1789
  },
1961
1790
  restore() {
@@ -2028,8 +1857,6 @@ var VectorMixin = {
2028
1857
  r = 0;
2029
1858
  }
2030
1859
  r = Math.min(r, 0.5 * w, 0.5 * h);
2031
-
2032
- // amount to inset control points from corners (see `ellipse`)
2033
1860
  const c = r * (1.0 - KAPPA);
2034
1861
  this.moveTo(x + r, y);
2035
1862
  this.lineTo(x + w - r, y);
@@ -2043,7 +1870,6 @@ var VectorMixin = {
2043
1870
  return this.closePath();
2044
1871
  },
2045
1872
  ellipse(x, y, r1, r2) {
2046
- // based on http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas/2173084#2173084
2047
1873
  if (r2 == null) {
2048
1874
  r2 = r1;
2049
1875
  }
@@ -2073,10 +1899,8 @@ var VectorMixin = {
2073
1899
  const HALF_PI = 0.5 * Math.PI;
2074
1900
  let deltaAng = endAngle - startAngle;
2075
1901
  if (Math.abs(deltaAng) > TWO_PI) {
2076
- // draw only full circle if more than that is specified
2077
1902
  deltaAng = TWO_PI;
2078
1903
  } else if (deltaAng !== 0 && anticlockwise !== deltaAng < 0) {
2079
- // necessary to flip direction of rendering
2080
1904
  const dir = anticlockwise ? -1 : 1;
2081
1905
  deltaAng = dir * TWO_PI + deltaAng;
2082
1906
  }
@@ -2084,38 +1908,21 @@ var VectorMixin = {
2084
1908
  const segAng = deltaAng / numSegs;
2085
1909
  const handleLen = segAng / HALF_PI * KAPPA * radius;
2086
1910
  let curAng = startAngle;
2087
-
2088
- // component distances between anchor point and control point
2089
1911
  let deltaCx = -Math.sin(curAng) * handleLen;
2090
1912
  let deltaCy = Math.cos(curAng) * handleLen;
2091
-
2092
- // anchor point
2093
1913
  let ax = x + Math.cos(curAng) * radius;
2094
1914
  let ay = y + Math.sin(curAng) * radius;
2095
-
2096
- // calculate and render segments
2097
1915
  this.moveTo(ax, ay);
2098
1916
  for (let segIdx = 0; segIdx < numSegs; segIdx++) {
2099
- // starting control point
2100
1917
  const cp1x = ax + deltaCx;
2101
1918
  const cp1y = ay + deltaCy;
2102
-
2103
- // step angle
2104
1919
  curAng += segAng;
2105
-
2106
- // next anchor point
2107
1920
  ax = x + Math.cos(curAng) * radius;
2108
1921
  ay = y + Math.sin(curAng) * radius;
2109
-
2110
- // next control point delta
2111
1922
  deltaCx = -Math.sin(curAng) * handleLen;
2112
1923
  deltaCy = Math.cos(curAng) * handleLen;
2113
-
2114
- // ending control point
2115
1924
  const cp2x = ax - deltaCx;
2116
1925
  const cp2y = ay - deltaCy;
2117
-
2118
- // render segment
2119
1926
  this.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, ax, ay);
2120
1927
  }
2121
1928
  return this;
@@ -2176,9 +1983,7 @@ var VectorMixin = {
2176
1983
  return this.addContent(`W${this._windingRule(rule)} n`);
2177
1984
  },
2178
1985
  transform(m11, m12, m21, m22, dx, dy) {
2179
- // keep track of the current transformation matrix
2180
1986
  if (m11 === 1 && m12 === 0 && m21 === 0 && m22 === 1 && dx === 0 && dy === 0) {
2181
- // Ignore identity transforms
2182
1987
  return this;
2183
1988
  }
2184
1989
  const m = this._ctm;
@@ -2342,7 +2147,6 @@ class AFMFont {
2342
2147
  this.boundingBoxes = {};
2343
2148
  this.kernPairs = {};
2344
2149
  this.parse();
2345
- // todo: remove charWidths since appears to not be used
2346
2150
  this.charWidths = new Array(256);
2347
2151
  for (let char = 0; char <= 255; char++) {
2348
2152
  this.charWidths[char] = this.glyphWidths[characters[char]];
@@ -2449,21 +2253,17 @@ class PDFFont {
2449
2253
  return;
2450
2254
  }
2451
2255
  this.embed();
2452
- return this.embedded = true;
2256
+ this.embedded = true;
2453
2257
  }
2454
2258
  embed() {
2455
2259
  throw new Error('Must be implemented by subclasses');
2456
2260
  }
2457
- lineHeight(size, includeGap) {
2458
- if (includeGap == null) {
2459
- includeGap = false;
2460
- }
2261
+ lineHeight(size, includeGap = false) {
2461
2262
  const gap = includeGap ? this.lineGap : 0;
2462
2263
  return (this.ascender + gap - this.descender) / 1000 * size;
2463
2264
  }
2464
2265
  }
2465
2266
 
2466
- // This insanity is so bundlers can inline the font files
2467
2267
  const STANDARD_FONTS = {
2468
2268
  Courier() {
2469
2269
  return fs.readFileSync(__dirname + '/data/Courier.afm', 'utf8');
@@ -2591,8 +2391,6 @@ class EmbeddedFont extends PDFFont {
2591
2391
  }
2592
2392
  layoutRun(text, features) {
2593
2393
  const run = this.font.layout(text, features);
2594
-
2595
- // Normalize position values
2596
2394
  for (let i = 0; i < run.positions.length; i++) {
2597
2395
  const position = run.positions[i];
2598
2396
  for (let key in position) {
@@ -2615,16 +2413,12 @@ class EmbeddedFont extends PDFFont {
2615
2413
  return run;
2616
2414
  }
2617
2415
  layout(text, features, onlyWidth) {
2618
- // Skip the cache if any user defined features are applied
2619
2416
  if (features) {
2620
2417
  return this.layoutRun(text, features);
2621
2418
  }
2622
2419
  let glyphs = onlyWidth ? null : [];
2623
2420
  let positions = onlyWidth ? null : [];
2624
2421
  let advanceWidth = 0;
2625
-
2626
- // Split the string by words to increase cache efficiency.
2627
- // For this purpose, spaces and tabs are a good enough delimeter.
2628
2422
  let last = 0;
2629
2423
  let index = 0;
2630
2424
  while (index <= text.length) {
@@ -2677,7 +2471,7 @@ class EmbeddedFont extends PDFFont {
2677
2471
  if (isCFF) {
2678
2472
  fontFile.data.Subtype = 'CIDFontType0C';
2679
2473
  }
2680
- this.subset.encodeStream().on('data', data => fontFile.write(data)).on('end', () => fontFile.end());
2474
+ fontFile.end(this.subset.encode());
2681
2475
  const familyClass = ((this.font['OS/2'] != null ? this.font['OS/2'].sFamilyClass : undefined) || 0) >> 8;
2682
2476
  let flags = 0;
2683
2477
  if (this.font.post.isFixedPitch) {
@@ -2686,17 +2480,15 @@ class EmbeddedFont extends PDFFont {
2686
2480
  if (1 <= familyClass && familyClass <= 7) {
2687
2481
  flags |= 1 << 1;
2688
2482
  }
2689
- flags |= 1 << 2; // assume the font uses non-latin characters
2483
+ flags |= 1 << 2;
2690
2484
  if (familyClass === 10) {
2691
2485
  flags |= 1 << 3;
2692
2486
  }
2693
2487
  if (this.font.head.macStyle.italic) {
2694
2488
  flags |= 1 << 6;
2695
2489
  }
2696
-
2697
- // generate a tag (6 uppercase letters. 17 is the char code offset from '0' to 'A'. 73 will map to 'Z')
2698
2490
  const tag = [1, 2, 3, 4, 5, 6].map(i => String.fromCharCode((this.id.charCodeAt(i) || 73) + 17)).join('');
2699
- const name = tag + '+' + this.font.postscriptName;
2491
+ const name = tag + '+' + this.font.postscriptName?.replaceAll(' ', '_');
2700
2492
  const {
2701
2493
  bbox
2702
2494
  } = this.font;
@@ -2711,8 +2503,7 @@ class EmbeddedFont extends PDFFont {
2711
2503
  CapHeight: (this.font.capHeight || this.font.ascent) * this.scale,
2712
2504
  XHeight: (this.font.xHeight || 0) * this.scale,
2713
2505
  StemV: 0
2714
- }); // not sure how to calculate this
2715
-
2506
+ });
2716
2507
  if (isCFF) {
2717
2508
  descriptor.data.FontFile3 = fontFile;
2718
2509
  } else {
@@ -2754,17 +2545,11 @@ class EmbeddedFont extends PDFFont {
2754
2545
  };
2755
2546
  return this.dictionary.end();
2756
2547
  }
2757
-
2758
- // Maps the glyph ids encoded in the PDF back to unicode strings
2759
- // Because of ligature substitutions and the like, there may be one or more
2760
- // unicode characters represented by each glyph.
2761
2548
  toUnicodeCmap() {
2762
2549
  const cmap = this.document.ref();
2763
2550
  const entries = [];
2764
2551
  for (let codePoints of this.unicode) {
2765
2552
  const encoded = [];
2766
-
2767
- // encode codePoints to utf16
2768
2553
  for (let value of codePoints) {
2769
2554
  if (value > 0xffff) {
2770
2555
  value -= 0x10000;
@@ -2818,12 +2603,10 @@ class PDFFontFactory {
2818
2603
  }
2819
2604
  src = fs.readFileSync(src);
2820
2605
  }
2821
- if (Buffer.isBuffer(src)) {
2606
+ if (src instanceof Uint8Array) {
2822
2607
  font = fontkit.create(src, family);
2823
- } else if (src instanceof Uint8Array) {
2824
- font = fontkit.create(Buffer.from(src), family);
2825
2608
  } else if (src instanceof ArrayBuffer) {
2826
- font = fontkit.create(Buffer.from(new Uint8Array(src)), family);
2609
+ font = fontkit.create(new Uint8Array(src), family);
2827
2610
  }
2828
2611
  if (font == null) {
2829
2612
  throw new Error('Not a supported font format or standard PDF font.');
@@ -2832,20 +2615,27 @@ class PDFFontFactory {
2832
2615
  }
2833
2616
  }
2834
2617
 
2618
+ const isEqualFont = (font1, font2) => {
2619
+ if (font1.font._tables?.head?.checkSumAdjustment !== font2.font._tables?.head?.checkSumAdjustment) {
2620
+ return false;
2621
+ }
2622
+ if (JSON.stringify(font1.font._tables?.name?.records) !== JSON.stringify(font2.font._tables?.name?.records)) {
2623
+ return false;
2624
+ }
2625
+ return true;
2626
+ };
2835
2627
  var FontsMixin = {
2836
- initFonts(defaultFont = 'Helvetica') {
2837
- // Lookup table for embedded fonts
2628
+ initFonts(defaultFont = 'Helvetica', defaultFontFamily = null, defaultFontSize = 12) {
2838
2629
  this._fontFamilies = {};
2839
2630
  this._fontCount = 0;
2840
-
2841
- // Font state
2842
- this._fontSize = 12;
2631
+ this._fontSource = defaultFont;
2632
+ this._fontFamily = defaultFontFamily;
2633
+ this._fontSize = defaultFontSize;
2843
2634
  this._font = null;
2635
+ this._remSize = defaultFontSize;
2844
2636
  this._registeredFonts = {};
2845
-
2846
- // Set the default font
2847
2637
  if (defaultFont) {
2848
- this.font(defaultFont);
2638
+ this.font(defaultFont, defaultFontFamily);
2849
2639
  }
2850
2640
  },
2851
2641
  font(src, family, size) {
@@ -2854,8 +2644,6 @@ var FontsMixin = {
2854
2644
  size = family;
2855
2645
  family = null;
2856
2646
  }
2857
-
2858
- // check registered fonts if src is a string
2859
2647
  if (typeof src === 'string' && this._registeredFonts[src]) {
2860
2648
  cacheKey = src;
2861
2649
  ({
@@ -2868,28 +2656,21 @@ var FontsMixin = {
2868
2656
  cacheKey = null;
2869
2657
  }
2870
2658
  }
2659
+ this._fontSource = src;
2660
+ this._fontFamily = family;
2871
2661
  if (size != null) {
2872
2662
  this.fontSize(size);
2873
2663
  }
2874
-
2875
- // fast path: check if the font is already in the PDF
2876
2664
  if (font = this._fontFamilies[cacheKey]) {
2877
2665
  this._font = font;
2878
2666
  return this;
2879
2667
  }
2880
-
2881
- // load the font
2882
2668
  const id = `F${++this._fontCount}`;
2883
2669
  this._font = PDFFontFactory.open(this, src, family, id);
2884
-
2885
- // check for existing font familes with the same name already in the PDF
2886
- // useful if the font was passed as a buffer
2887
- if (font = this._fontFamilies[this._font.name]) {
2670
+ if ((font = this._fontFamilies[this._font.name]) && isEqualFont(this._font, font)) {
2888
2671
  this._font = font;
2889
2672
  return this;
2890
2673
  }
2891
-
2892
- // save the font for reuse later
2893
2674
  if (cacheKey) {
2894
2675
  this._fontFamilies[cacheKey] = this._font;
2895
2676
  }
@@ -2899,13 +2680,10 @@ var FontsMixin = {
2899
2680
  return this;
2900
2681
  },
2901
2682
  fontSize(_fontSize) {
2902
- this._fontSize = _fontSize;
2683
+ this._fontSize = this.sizeToPoint(_fontSize);
2903
2684
  return this;
2904
2685
  },
2905
2686
  currentLineHeight(includeGap) {
2906
- if (includeGap == null) {
2907
- includeGap = false;
2908
- }
2909
2687
  return this._font.lineHeight(this._fontSize, includeGap);
2910
2688
  },
2911
2689
  registerFont(name, src, family) {
@@ -2914,6 +2692,64 @@ var FontsMixin = {
2914
2692
  family
2915
2693
  };
2916
2694
  return this;
2695
+ },
2696
+ sizeToPoint(size, defaultValue = 0, page = this.page, percentageWidth = undefined) {
2697
+ if (!percentageWidth) percentageWidth = this._fontSize;
2698
+ if (typeof defaultValue !== 'number') defaultValue = this.sizeToPoint(defaultValue);
2699
+ if (size === undefined) return defaultValue;
2700
+ if (typeof size === 'number') return size;
2701
+ if (typeof size === 'boolean') return Number(size);
2702
+ const match = String(size).match(/((\d+)?(\.\d+)?)(em|in|px|cm|mm|pc|ex|ch|rem|vw|vh|vmin|vmax|%|pt)?/);
2703
+ if (!match) throw new Error(`Unsupported size '${size}'`);
2704
+ let multiplier;
2705
+ switch (match[4]) {
2706
+ case 'em':
2707
+ multiplier = this._fontSize;
2708
+ break;
2709
+ case 'in':
2710
+ multiplier = IN_TO_PT;
2711
+ break;
2712
+ case 'px':
2713
+ multiplier = PX_TO_IN * IN_TO_PT;
2714
+ break;
2715
+ case 'cm':
2716
+ multiplier = CM_TO_IN * IN_TO_PT;
2717
+ break;
2718
+ case 'mm':
2719
+ multiplier = MM_TO_CM * CM_TO_IN * IN_TO_PT;
2720
+ break;
2721
+ case 'pc':
2722
+ multiplier = PC_TO_PT;
2723
+ break;
2724
+ case 'ex':
2725
+ multiplier = this.currentLineHeight();
2726
+ break;
2727
+ case 'ch':
2728
+ multiplier = this.widthOfString('0');
2729
+ break;
2730
+ case 'rem':
2731
+ multiplier = this._remSize;
2732
+ break;
2733
+ case 'vw':
2734
+ multiplier = page.width / 100;
2735
+ break;
2736
+ case 'vh':
2737
+ multiplier = page.height / 100;
2738
+ break;
2739
+ case 'vmin':
2740
+ multiplier = Math.min(page.width, page.height) / 100;
2741
+ break;
2742
+ case 'vmax':
2743
+ multiplier = Math.max(page.width, page.height) / 100;
2744
+ break;
2745
+ case '%':
2746
+ multiplier = percentageWidth / 100;
2747
+ break;
2748
+ case 'pt':
2749
+ default:
2750
+ multiplier = 1;
2751
+ }
2752
+ return multiplier * Number(match[1]);
2917
2753
  }
2918
2754
  };
2919
2755
 
@@ -2923,12 +2759,13 @@ class LineWrapper extends events.EventEmitter {
2923
2759
  constructor(document, options) {
2924
2760
  super();
2925
2761
  this.document = document;
2926
- this.indent = options.indent || 0;
2927
- this.characterSpacing = options.characterSpacing || 0;
2928
- this.wordSpacing = options.wordSpacing === 0;
2762
+ this.horizontalScaling = options.horizontalScaling || 100;
2763
+ this.indent = (options.indent || 0) * this.horizontalScaling / 100;
2764
+ this.characterSpacing = (options.characterSpacing || 0) * this.horizontalScaling / 100;
2765
+ this.wordSpacing = (options.wordSpacing === 0) * this.horizontalScaling / 100;
2929
2766
  this.columns = options.columns || 1;
2930
- this.columnGap = options.columnGap != null ? options.columnGap : 18; // 1/4 inch
2931
- this.lineWidth = (options.width - this.columnGap * (this.columns - 1)) / this.columns;
2767
+ this.columnGap = (options.columnGap != null ? options.columnGap : 18) * this.horizontalScaling / 100;
2768
+ this.lineWidth = (options.width * this.horizontalScaling / 100 - this.columnGap * (this.columns - 1)) / this.columns;
2932
2769
  this.spaceLeft = this.lineWidth;
2933
2770
  this.startX = this.document.x;
2934
2771
  this.startY = this.document.y;
@@ -2936,36 +2773,30 @@ class LineWrapper extends events.EventEmitter {
2936
2773
  this.ellipsis = options.ellipsis;
2937
2774
  this.continuedX = 0;
2938
2775
  this.features = options.features;
2939
-
2940
- // calculate the maximum Y position the text can appear at
2941
2776
  if (options.height != null) {
2942
2777
  this.height = options.height;
2943
- this.maxY = this.startY + options.height;
2778
+ this.maxY = PDFNumber(this.startY + options.height);
2944
2779
  } else {
2945
- this.maxY = this.document.page.maxY();
2780
+ this.maxY = PDFNumber(this.document.page.maxY());
2946
2781
  }
2947
-
2948
- // handle paragraph indents
2949
2782
  this.on('firstLine', options => {
2950
- // if this is the first line of the text segment, and
2951
- // we're continuing where we left off, indent that much
2952
- // otherwise use the user specified indent option
2953
2783
  const indent = this.continuedX || this.indent;
2954
2784
  this.document.x += indent;
2955
2785
  this.lineWidth -= indent;
2956
- return this.once('line', () => {
2786
+ if (options.indentAllLines) {
2787
+ return;
2788
+ }
2789
+ this.once('line', () => {
2957
2790
  this.document.x -= indent;
2958
2791
  this.lineWidth += indent;
2959
2792
  if (options.continued && !this.continuedX) {
2960
2793
  this.continuedX = this.indent;
2961
2794
  }
2962
2795
  if (!options.continued) {
2963
- return this.continuedX = 0;
2796
+ this.continuedX = 0;
2964
2797
  }
2965
2798
  });
2966
2799
  });
2967
-
2968
- // handle left aligning last lines of paragraphs
2969
2800
  this.on('lastLine', options => {
2970
2801
  const {
2971
2802
  align
@@ -2974,7 +2805,7 @@ class LineWrapper extends events.EventEmitter {
2974
2805
  options.align = 'left';
2975
2806
  }
2976
2807
  this.lastLine = true;
2977
- return this.once('line', () => {
2808
+ this.once('line', () => {
2978
2809
  this.document.y += options.paragraphGap || 0;
2979
2810
  options.align = align;
2980
2811
  return this.lastLine = false;
@@ -2991,7 +2822,6 @@ class LineWrapper extends events.EventEmitter {
2991
2822
  return w + this.wordWidth(HYPHEN) <= this.spaceLeft;
2992
2823
  }
2993
2824
  eachWord(text, fn) {
2994
- // setup a unicode line breaker
2995
2825
  let bk;
2996
2826
  const breaker = new LineBreaker(text);
2997
2827
  let last = null;
@@ -3000,19 +2830,12 @@ class LineWrapper extends events.EventEmitter {
3000
2830
  var shouldContinue;
3001
2831
  let word = text.slice((last != null ? last.position : undefined) || 0, bk.position);
3002
2832
  let w = wordWidths[word] != null ? wordWidths[word] : wordWidths[word] = this.wordWidth(word);
3003
-
3004
- // if the word is longer than the whole line, chop it up
3005
- // TODO: break by grapheme clusters, not JS string characters
3006
2833
  if (w > this.lineWidth + this.continuedX) {
3007
- // make some fake break objects
3008
2834
  let lbk = last;
3009
2835
  const fbk = {};
3010
2836
  while (word.length) {
3011
- // fit as much of the word as possible into the space we have
3012
2837
  var l, mightGrow;
3013
2838
  if (w > this.spaceLeft) {
3014
- // start our check at the end of our available space - this method is faster than a loop of each character and it resolves
3015
- // an issue with long loops when processing massive words, such as a huge number of spaces
3016
2839
  l = Math.ceil(this.spaceLeft / (w / word.length));
3017
2840
  w = this.wordWidth(word.slice(0, l));
3018
2841
  mightGrow = w <= this.spaceLeft && l < word.length;
@@ -3020,7 +2843,6 @@ class LineWrapper extends events.EventEmitter {
3020
2843
  l = word.length;
3021
2844
  }
3022
2845
  let mustShrink = w > this.spaceLeft && l > 0;
3023
- // shrink or grow word as necessary after our near-guess above
3024
2846
  while (mustShrink || mightGrow) {
3025
2847
  if (mustShrink) {
3026
2848
  w = this.wordWidth(word.slice(0, --l));
@@ -3031,20 +2853,14 @@ class LineWrapper extends events.EventEmitter {
3031
2853
  mightGrow = w <= this.spaceLeft && l < word.length;
3032
2854
  }
3033
2855
  }
3034
-
3035
- // check for the edge case where a single character cannot fit into a line.
3036
2856
  if (l === 0 && this.spaceLeft === this.lineWidth) {
3037
2857
  l = 1;
3038
2858
  }
3039
-
3040
- // send a required break unless this is the last piece and a linebreak is not specified
3041
2859
  fbk.required = bk.required || l < word.length;
3042
2860
  shouldContinue = fn(word.slice(0, l), w, fbk, lbk);
3043
2861
  lbk = {
3044
2862
  required: false
3045
2863
  };
3046
-
3047
- // get the remaining piece of the word
3048
2864
  word = word.slice(l);
3049
2865
  w = this.wordWidth(word);
3050
2866
  if (shouldContinue === false) {
@@ -3052,7 +2868,6 @@ class LineWrapper extends events.EventEmitter {
3052
2868
  }
3053
2869
  }
3054
2870
  } else {
3055
- // otherwise just emit the break as it was given to us
3056
2871
  shouldContinue = fn(word, w, bk, last);
3057
2872
  }
3058
2873
  if (shouldContinue === false) {
@@ -3062,23 +2877,19 @@ class LineWrapper extends events.EventEmitter {
3062
2877
  }
3063
2878
  }
3064
2879
  wrap(text, options) {
3065
- // override options from previous continued fragments
2880
+ this.horizontalScaling = options.horizontalScaling || 100;
3066
2881
  if (options.indent != null) {
3067
- this.indent = options.indent;
2882
+ this.indent = options.indent * this.horizontalScaling / 100;
3068
2883
  }
3069
2884
  if (options.characterSpacing != null) {
3070
- this.characterSpacing = options.characterSpacing;
2885
+ this.characterSpacing = options.characterSpacing * this.horizontalScaling / 100;
3071
2886
  }
3072
2887
  if (options.wordSpacing != null) {
3073
- this.wordSpacing = options.wordSpacing;
2888
+ this.wordSpacing = options.wordSpacing * this.horizontalScaling / 100;
3074
2889
  }
3075
2890
  if (options.ellipsis != null) {
3076
2891
  this.ellipsis = options.ellipsis;
3077
2892
  }
3078
-
3079
- // make sure we're actually on the page
3080
- // and that the first line of is never by
3081
- // itself at the bottom of a page (orphans)
3082
2893
  const nextY = this.document.y + this.document.currentLineHeight(true);
3083
2894
  if (this.document.y > this.maxY || nextY > this.maxY) {
3084
2895
  this.nextSection();
@@ -3089,7 +2900,7 @@ class LineWrapper extends events.EventEmitter {
3089
2900
  let lc = 0;
3090
2901
  let {
3091
2902
  y
3092
- } = this.document; // used to reset Y pos if options.continued (below)
2903
+ } = this.document;
3093
2904
  const emitLine = () => {
3094
2905
  options.textWidth = textWidth + this.wordSpacing * (wc - 1);
3095
2906
  options.wordCount = wc;
@@ -3112,23 +2923,17 @@ class LineWrapper extends events.EventEmitter {
3112
2923
  wc++;
3113
2924
  }
3114
2925
  if (bk.required || !this.canFit(word, w)) {
3115
- // if the user specified a max height and an ellipsis, and is about to pass the
3116
- // max height and max columns after the next line, append the ellipsis
3117
2926
  const lh = this.document.currentLineHeight(true);
3118
- if (this.height != null && this.ellipsis && this.document.y + lh * 2 > this.maxY && this.column >= this.columns) {
2927
+ if (this.height != null && this.ellipsis && PDFNumber(this.document.y + lh * 2) > this.maxY && this.column >= this.columns) {
3119
2928
  if (this.ellipsis === true) {
3120
2929
  this.ellipsis = '…';
3121
- } // map default ellipsis character
2930
+ }
3122
2931
  buffer = buffer.replace(/\s+$/, '');
3123
2932
  textWidth = this.wordWidth(buffer + this.ellipsis);
3124
-
3125
- // remove characters from the buffer until the ellipsis fits
3126
- // to avoid infinite loop need to stop while-loop if buffer is empty string
3127
2933
  while (buffer && textWidth > this.lineWidth) {
3128
2934
  buffer = buffer.slice(0, -1).replace(/\s+$/, '');
3129
2935
  textWidth = this.wordWidth(buffer + this.ellipsis);
3130
2936
  }
3131
- // need to add ellipsis only if there is enough space for it
3132
2937
  if (textWidth <= this.lineWidth) {
3133
2938
  buffer = buffer + this.ellipsis;
3134
2939
  }
@@ -3143,35 +2948,25 @@ class LineWrapper extends events.EventEmitter {
3143
2948
  }
3144
2949
  this.emit('lastLine', options, this);
3145
2950
  }
3146
-
3147
- // Previous entry is a soft hyphen - add visible hyphen.
3148
2951
  if (buffer[buffer.length - 1] == SOFT_HYPHEN) {
3149
2952
  buffer = buffer.slice(0, -1) + HYPHEN;
3150
2953
  this.spaceLeft -= this.wordWidth(HYPHEN);
3151
2954
  }
3152
2955
  emitLine();
3153
-
3154
- // if we've reached the edge of the page,
3155
- // continue on a new page or column
3156
- if (this.document.y + lh > this.maxY) {
2956
+ if (PDFNumber(this.document.y + lh) > this.maxY) {
3157
2957
  const shouldContinue = this.nextSection();
3158
-
3159
- // stop if we reached the maximum height
3160
2958
  if (!shouldContinue) {
3161
2959
  wc = 0;
3162
2960
  buffer = '';
3163
2961
  return false;
3164
2962
  }
3165
2963
  }
3166
-
3167
- // reset the space left and buffer
3168
2964
  if (bk.required) {
3169
2965
  this.spaceLeft = this.lineWidth;
3170
2966
  buffer = '';
3171
2967
  textWidth = 0;
3172
2968
  return wc = 0;
3173
2969
  } else {
3174
- // reset the space left and buffer
3175
2970
  this.spaceLeft = this.lineWidth - w;
3176
2971
  buffer = word;
3177
2972
  textWidth = w;
@@ -3186,25 +2981,19 @@ class LineWrapper extends events.EventEmitter {
3186
2981
  emitLine();
3187
2982
  }
3188
2983
  this.emit('sectionEnd', options, this);
3189
-
3190
- // if the wrap is set to be continued, save the X position
3191
- // to start the first line of the next segment at, and reset
3192
- // the y position
3193
2984
  if (options.continued === true) {
3194
2985
  if (lc > 1) {
3195
2986
  this.continuedX = 0;
3196
2987
  }
3197
2988
  this.continuedX += options.textWidth || 0;
3198
- return this.document.y = y;
2989
+ this.document.y = y;
3199
2990
  } else {
3200
- return this.document.x = this.startX;
2991
+ this.document.x = this.startX;
3201
2992
  }
3202
2993
  }
3203
2994
  nextSection(options) {
3204
2995
  this.emit('sectionEnd', options, this);
3205
2996
  if (++this.column > this.columns) {
3206
- // if a max height was specified by the user, we're done.
3207
- // otherwise, the default is to make a new page at the bottom.
3208
2997
  if (this.height != null) {
3209
2998
  return false;
3210
2999
  }
@@ -3228,15 +3017,14 @@ class LineWrapper extends events.EventEmitter {
3228
3017
  }
3229
3018
 
3230
3019
  const {
3231
- number: number$2
3020
+ number
3232
3021
  } = PDFObject;
3233
3022
  var TextMixin = {
3234
3023
  initText() {
3235
3024
  this._line = this._line.bind(this);
3236
- // Current coordinates
3237
3025
  this.x = 0;
3238
3026
  this.y = 0;
3239
- return this._lineGap = 0;
3027
+ this._lineGap = 0;
3240
3028
  },
3241
3029
  lineGap(_lineGap) {
3242
3030
  this._lineGap = _lineGap;
@@ -3258,11 +3046,7 @@ var TextMixin = {
3258
3046
  },
3259
3047
  _text(text, x, y, options, lineCallback) {
3260
3048
  options = this._initOptions(x, y, options);
3261
-
3262
- // Convert text to a string
3263
3049
  text = text == null ? '' : `${text}`;
3264
-
3265
- // if the wordSpacing option is specified, remove multiple consecutive spaces
3266
3050
  if (options.wordSpacing) {
3267
3051
  text = text.replace(/\s{2,}/g, ' ');
3268
3052
  }
@@ -3271,8 +3055,12 @@ var TextMixin = {
3271
3055
  options.structParent.add(this.struct(options.structType || 'P', [this.markStructureContent(options.structType || 'P')]));
3272
3056
  }
3273
3057
  };
3274
-
3275
- // word wrapping
3058
+ if (options.rotation !== 0) {
3059
+ this.save();
3060
+ this.rotate(-options.rotation, {
3061
+ origin: [this.x, this.y]
3062
+ });
3063
+ }
3276
3064
  if (options.width) {
3277
3065
  let wrapper = this._wrapper;
3278
3066
  if (!wrapper) {
@@ -3283,21 +3071,113 @@ var TextMixin = {
3283
3071
  this._wrapper = options.continued ? wrapper : null;
3284
3072
  this._textOptions = options.continued ? options : null;
3285
3073
  wrapper.wrap(text, options);
3286
-
3287
- // render paragraphs as single lines
3288
3074
  } else {
3289
3075
  for (let line of text.split('\n')) {
3290
3076
  addStructure();
3291
3077
  lineCallback(line, options);
3292
3078
  }
3293
3079
  }
3080
+ if (options.rotation !== 0) this.restore();
3294
3081
  return this;
3295
3082
  },
3296
3083
  text(text, x, y, options) {
3297
3084
  return this._text(text, x, y, options, this._line);
3298
3085
  },
3299
3086
  widthOfString(string, options = {}) {
3300
- return this._font.widthOfString(string, this._fontSize, options.features) + (options.characterSpacing || 0) * (string.length - 1);
3087
+ const horizontalScaling = options.horizontalScaling || 100;
3088
+ return (this._font.widthOfString(string, this._fontSize, options.features) + (options.characterSpacing || 0) * (string.length - 1)) * horizontalScaling / 100;
3089
+ },
3090
+ boundsOfString(string, x, y, options) {
3091
+ options = this._initOptions(x, y, options);
3092
+ ({
3093
+ x,
3094
+ y
3095
+ } = this);
3096
+ const lineGap = options.lineGap ?? this._lineGap ?? 0;
3097
+ const lineHeight = this.currentLineHeight(true) + lineGap;
3098
+ let contentWidth = 0;
3099
+ string = String(string ?? '');
3100
+ if (options.wordSpacing) {
3101
+ string = string.replace(/\s{2,}/g, ' ');
3102
+ }
3103
+ if (options.width) {
3104
+ let wrapper = new LineWrapper(this, options);
3105
+ wrapper.on('line', (text, options) => {
3106
+ this.y += lineHeight;
3107
+ text = text.replace(/\n/g, '');
3108
+ if (text.length) {
3109
+ let wordSpacing = options.wordSpacing ?? 0;
3110
+ const characterSpacing = options.characterSpacing ?? 0;
3111
+ if (options.width && options.align === 'justify') {
3112
+ const words = text.trim().split(/\s+/);
3113
+ const textWidth = this.widthOfString(text.replace(/\s+/g, ''), options);
3114
+ const spaceWidth = this.widthOfString(' ') + characterSpacing;
3115
+ wordSpacing = Math.max(0, (options.lineWidth - textWidth) / Math.max(1, words.length - 1) - spaceWidth);
3116
+ }
3117
+ contentWidth = Math.max(contentWidth, options.textWidth + wordSpacing * (options.wordCount - 1) + characterSpacing * (text.length - 1));
3118
+ }
3119
+ });
3120
+ wrapper.wrap(string, options);
3121
+ } else {
3122
+ for (let line of string.split('\n')) {
3123
+ const lineWidth = this.widthOfString(line, options);
3124
+ this.y += lineHeight;
3125
+ contentWidth = Math.max(contentWidth, lineWidth);
3126
+ }
3127
+ }
3128
+ let contentHeight = this.y - y;
3129
+ if (options.height) contentHeight = Math.min(contentHeight, options.height);
3130
+ this.x = x;
3131
+ this.y = y;
3132
+ if (options.rotation === 0) {
3133
+ return {
3134
+ x,
3135
+ y,
3136
+ width: contentWidth,
3137
+ height: contentHeight
3138
+ };
3139
+ } else if (options.rotation === 90) {
3140
+ return {
3141
+ x: x,
3142
+ y: y - contentWidth,
3143
+ width: contentHeight,
3144
+ height: contentWidth
3145
+ };
3146
+ } else if (options.rotation === 180) {
3147
+ return {
3148
+ x: x - contentWidth,
3149
+ y: y - contentHeight,
3150
+ width: contentWidth,
3151
+ height: contentHeight
3152
+ };
3153
+ } else if (options.rotation === 270) {
3154
+ return {
3155
+ x: x - contentHeight,
3156
+ y: y,
3157
+ width: contentHeight,
3158
+ height: contentWidth
3159
+ };
3160
+ }
3161
+ const cos = cosine(options.rotation);
3162
+ const sin = sine(options.rotation);
3163
+ const x1 = x;
3164
+ const y1 = y;
3165
+ const x2 = x + contentWidth * cos;
3166
+ const y2 = y - contentWidth * sin;
3167
+ const x3 = x + contentWidth * cos + contentHeight * sin;
3168
+ const y3 = y - contentWidth * sin + contentHeight * cos;
3169
+ const x4 = x + contentHeight * sin;
3170
+ const y4 = y + contentHeight * cos;
3171
+ const xMin = Math.min(x1, x2, x3, x4);
3172
+ const xMax = Math.max(x1, x2, x3, x4);
3173
+ const yMin = Math.min(y1, y2, y3, y4);
3174
+ const yMax = Math.max(y1, y2, y3, y4);
3175
+ return {
3176
+ x: xMin,
3177
+ y: yMin,
3178
+ width: xMax - xMin,
3179
+ height: yMax - yMin
3180
+ };
3301
3181
  },
3302
3182
  heightOfString(text, options) {
3303
3183
  const {
@@ -3305,11 +3185,10 @@ var TextMixin = {
3305
3185
  y
3306
3186
  } = this;
3307
3187
  options = this._initOptions(options);
3308
- options.height = Infinity; // don't break pages
3309
-
3188
+ options.height = Infinity;
3310
3189
  const lineGap = options.lineGap || this._lineGap || 0;
3311
3190
  this._text(text, this.x, this.y, options, () => {
3312
- return this.y += this.currentLineHeight(true) + lineGap;
3191
+ this.y += this.currentLineHeight(true) + lineGap;
3313
3192
  });
3314
3193
  const height = this.y - y;
3315
3194
  this.x = x;
@@ -3407,12 +3286,12 @@ var TextMixin = {
3407
3286
  wrapper.on('sectionStart', () => {
3408
3287
  const pos = indent + itemIndent * (level - 1);
3409
3288
  this.x += pos;
3410
- return wrapper.lineWidth -= pos;
3289
+ wrapper.lineWidth -= pos;
3411
3290
  });
3412
3291
  wrapper.on('sectionEnd', () => {
3413
3292
  const pos = indent + itemIndent * (level - 1);
3414
3293
  this.x -= pos;
3415
- return wrapper.lineWidth += pos;
3294
+ wrapper.lineWidth += pos;
3416
3295
  });
3417
3296
  wrapper.wrap(listItem, options);
3418
3297
  };
@@ -3426,11 +3305,7 @@ var TextMixin = {
3426
3305
  options = x;
3427
3306
  x = null;
3428
3307
  }
3429
-
3430
- // clone options object
3431
3308
  const result = Object.assign({}, options);
3432
-
3433
- // extend options with previous values for continued text
3434
3309
  if (this._textOptions) {
3435
3310
  for (let key in this._textOptions) {
3436
3311
  const val = this._textOptions[key];
@@ -3441,16 +3316,12 @@ var TextMixin = {
3441
3316
  }
3442
3317
  }
3443
3318
  }
3444
-
3445
- // Update the current position
3446
3319
  if (x != null) {
3447
3320
  this.x = x;
3448
3321
  }
3449
3322
  if (y != null) {
3450
3323
  this.y = y;
3451
3324
  }
3452
-
3453
- // wrap to margins if no x or y position passed
3454
3325
  if (result.lineBreak !== false) {
3455
3326
  if (result.width == null) {
3456
3327
  result.width = this.page.width - this.x - this.page.margins.right;
@@ -3462,17 +3333,18 @@ var TextMixin = {
3462
3333
  }
3463
3334
  if (result.columnGap == null) {
3464
3335
  result.columnGap = 18;
3465
- } // 1/4 inch
3466
-
3336
+ }
3337
+ result.rotation = Number(options.rotation ?? 0) % 360;
3338
+ if (result.rotation < 0) result.rotation += 360;
3467
3339
  return result;
3468
3340
  },
3469
3341
  _line(text, options = {}, wrapper) {
3470
3342
  this._fragment(text, this.x, this.y, options);
3471
3343
  const lineGap = options.lineGap || this._lineGap || 0;
3472
3344
  if (!wrapper) {
3473
- return this.x += this.widthOfString(text);
3345
+ this.x += this.widthOfString(text, options);
3474
3346
  } else {
3475
- return this.y += this.currentLineHeight(true) + lineGap;
3347
+ this.y += this.currentLineHeight(true) + lineGap;
3476
3348
  }
3477
3349
  },
3478
3350
  _fragment(text, x, y, options) {
@@ -3481,13 +3353,10 @@ var TextMixin = {
3481
3353
  if (text.length === 0) {
3482
3354
  return;
3483
3355
  }
3484
-
3485
- // handle options
3486
3356
  const align = options.align || 'left';
3487
3357
  let wordSpacing = options.wordSpacing || 0;
3488
3358
  const characterSpacing = options.characterSpacing || 0;
3489
-
3490
- // text alignments
3359
+ const horizontalScaling = options.horizontalScaling || 100;
3491
3360
  if (options.width) {
3492
3361
  switch (align) {
3493
3362
  case 'right':
@@ -3498,7 +3367,6 @@ var TextMixin = {
3498
3367
  x += options.lineWidth / 2 - options.textWidth / 2;
3499
3368
  break;
3500
3369
  case 'justify':
3501
- // calculate the word spacing value
3502
3370
  words = text.trim().split(/\s+/);
3503
3371
  textWidth = this.widthOfString(text.replace(/\s+/g, ''), options);
3504
3372
  var spaceWidth = this.widthOfString(' ') + characterSpacing;
@@ -3506,8 +3374,6 @@ var TextMixin = {
3506
3374
  break;
3507
3375
  }
3508
3376
  }
3509
-
3510
- // text baseline alignments based on http://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling
3511
3377
  if (typeof options.baseline === 'number') {
3512
3378
  dy = -options.baseline;
3513
3379
  } else {
@@ -3540,11 +3406,7 @@ var TextMixin = {
3540
3406
  }
3541
3407
  dy = dy / 1000 * this._fontSize;
3542
3408
  }
3543
-
3544
- // calculate the actual rendered width of the string after word and character spacing
3545
3409
  const renderedWidth = options.textWidth + wordSpacing * (options.wordCount - 1) + characterSpacing * (text.length - 1);
3546
-
3547
- // create link annotations if the link option is given
3548
3410
  if (options.link != null) {
3549
3411
  this.link(x, y, renderedWidth, this.currentLineHeight(), options.link);
3550
3412
  }
@@ -3554,8 +3416,6 @@ var TextMixin = {
3554
3416
  if (options.destination != null) {
3555
3417
  this.addNamedDestination(options.destination, 'XYZ', x, y, null);
3556
3418
  }
3557
-
3558
- // create underline
3559
3419
  if (options.underline) {
3560
3420
  this.save();
3561
3421
  if (!options.stroke) {
@@ -3569,8 +3429,6 @@ var TextMixin = {
3569
3429
  this.stroke();
3570
3430
  this.restore();
3571
3431
  }
3572
-
3573
- // create strikethrough line
3574
3432
  if (options.strike) {
3575
3433
  this.save();
3576
3434
  if (!options.stroke) {
@@ -3585,8 +3443,6 @@ var TextMixin = {
3585
3443
  this.restore();
3586
3444
  }
3587
3445
  this.save();
3588
-
3589
- // oblique (angle in degrees or boolean)
3590
3446
  if (options.oblique) {
3591
3447
  let skew;
3592
3448
  if (typeof options.oblique === 'number') {
@@ -3598,40 +3454,24 @@ var TextMixin = {
3598
3454
  this.transform(1, 0, skew, 1, -skew * dy, 0);
3599
3455
  this.transform(1, 0, 0, 1, -x, -y);
3600
3456
  }
3601
-
3602
- // flip coordinate system
3603
3457
  this.transform(1, 0, 0, -1, 0, this.page.height);
3604
3458
  y = this.page.height - y - dy;
3605
-
3606
- // add current font to page if necessary
3607
3459
  if (this.page.fonts[this._font.id] == null) {
3608
3460
  this.page.fonts[this._font.id] = this._font.ref();
3609
3461
  }
3610
-
3611
- // begin the text object
3612
3462
  this.addContent('BT');
3613
-
3614
- // text position
3615
- this.addContent(`1 0 0 1 ${number$2(x)} ${number$2(y)} Tm`);
3616
-
3617
- // font and font size
3618
- this.addContent(`/${this._font.id} ${number$2(this._fontSize)} Tf`);
3619
-
3620
- // rendering mode
3463
+ this.addContent(`1 0 0 1 ${number(x)} ${number(y)} Tm`);
3464
+ this.addContent(`/${this._font.id} ${number(this._fontSize)} Tf`);
3621
3465
  const mode = options.fill && options.stroke ? 2 : options.stroke ? 1 : 0;
3622
3466
  if (mode) {
3623
3467
  this.addContent(`${mode} Tr`);
3624
3468
  }
3625
-
3626
- // Character spacing
3627
3469
  if (characterSpacing) {
3628
- this.addContent(`${number$2(characterSpacing)} Tc`);
3470
+ this.addContent(`${number(characterSpacing)} Tc`);
3471
+ }
3472
+ if (horizontalScaling !== 100) {
3473
+ this.addContent(`${horizontalScaling} Tz`);
3629
3474
  }
3630
-
3631
- // Add the actual text
3632
- // If we have a word spacing value, we need to encode each word separately
3633
- // since the normal Tw operator only works on character code 32, which isn't
3634
- // used for embedded fonts.
3635
3475
  if (wordSpacing) {
3636
3476
  words = text.trim().split(/\s+/);
3637
3477
  wordSpacing += this.widthOfString(' ') + characterSpacing;
@@ -3642,9 +3482,6 @@ var TextMixin = {
3642
3482
  const [encodedWord, positionsWord] = this._font.encode(word, options.features);
3643
3483
  encoded = encoded.concat(encodedWord);
3644
3484
  positions = positions.concat(positionsWord);
3645
-
3646
- // add the word spacing to the end of the word
3647
- // clone object because of cache
3648
3485
  const space = {};
3649
3486
  const object = positions[positions.length - 1];
3650
3487
  for (let key in object) {
@@ -3661,60 +3498,42 @@ var TextMixin = {
3661
3498
  const commands = [];
3662
3499
  let last = 0;
3663
3500
  let hadOffset = false;
3664
-
3665
- // Adds a segment of text to the TJ command buffer
3666
3501
  const addSegment = cur => {
3667
3502
  if (last < cur) {
3668
3503
  const hex = encoded.slice(last, cur).join('');
3669
3504
  const advance = positions[cur - 1].xAdvance - positions[cur - 1].advanceWidth;
3670
- commands.push(`<${hex}> ${number$2(-advance)}`);
3505
+ commands.push(`<${hex}> ${number(-advance)}`);
3671
3506
  }
3672
- return last = cur;
3507
+ last = cur;
3673
3508
  };
3674
-
3675
- // Flushes the current TJ commands to the output stream
3676
3509
  const flush = i => {
3677
3510
  addSegment(i);
3678
3511
  if (commands.length > 0) {
3679
3512
  this.addContent(`[${commands.join(' ')}] TJ`);
3680
- return commands.length = 0;
3513
+ commands.length = 0;
3681
3514
  }
3682
3515
  };
3683
3516
  for (i = 0; i < positions.length; i++) {
3684
- // If we have an x or y offset, we have to break out of the current TJ command
3685
- // so we can move the text position.
3686
3517
  const pos = positions[i];
3687
3518
  if (pos.xOffset || pos.yOffset) {
3688
- // Flush the current buffer
3689
3519
  flush(i);
3690
-
3691
- // Move the text position and flush just the current character
3692
- this.addContent(`1 0 0 1 ${number$2(x + pos.xOffset * scale)} ${number$2(y + pos.yOffset * scale)} Tm`);
3520
+ this.addContent(`1 0 0 1 ${number(x + pos.xOffset * scale)} ${number(y + pos.yOffset * scale)} Tm`);
3693
3521
  flush(i + 1);
3694
3522
  hadOffset = true;
3695
3523
  } else {
3696
- // If the last character had an offset, reset the text position
3697
3524
  if (hadOffset) {
3698
- this.addContent(`1 0 0 1 ${number$2(x)} ${number$2(y)} Tm`);
3525
+ this.addContent(`1 0 0 1 ${number(x)} ${number(y)} Tm`);
3699
3526
  hadOffset = false;
3700
3527
  }
3701
-
3702
- // Group segments that don't have any advance adjustments
3703
3528
  if (pos.xAdvance - pos.advanceWidth !== 0) {
3704
3529
  addSegment(i + 1);
3705
3530
  }
3706
3531
  }
3707
3532
  x += pos.xAdvance * scale;
3708
3533
  }
3709
-
3710
- // Flush any remaining commands
3711
3534
  flush(i);
3712
-
3713
- // end the text object
3714
3535
  this.addContent('ET');
3715
-
3716
- // restore flipped coordinate system
3717
- return this.restore();
3536
+ this.restore();
3718
3537
  }
3719
3538
  };
3720
3539
 
@@ -3732,8 +3551,6 @@ class JPEG {
3732
3551
  if (this.data.readUInt16BE(0) !== 0xffd8) {
3733
3552
  throw 'SOI not found in JPEG';
3734
3553
  }
3735
-
3736
- // Parse the EXIF orientation
3737
3554
  this.orientation = exif.fromBuffer(this.data).Orientation || 1;
3738
3555
  let pos = 2;
3739
3556
  while (pos < this.data.length) {
@@ -3770,16 +3587,10 @@ class JPEG {
3770
3587
  ColorSpace: this.colorSpace,
3771
3588
  Filter: 'DCTDecode'
3772
3589
  });
3773
-
3774
- // add extra decode params for CMYK images. By swapping the
3775
- // min and max values from the default, we invert the colors. See
3776
- // section 4.8.4 of the spec.
3777
3590
  if (this.colorSpace === 'DeviceCMYK') {
3778
3591
  this.obj.data['Decode'] = [1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0];
3779
3592
  }
3780
3593
  this.obj.end(this.data);
3781
-
3782
- // free memory
3783
3594
  return this.data = null;
3784
3595
  }
3785
3596
  }
@@ -3822,24 +3633,14 @@ class PNGImage {
3822
3633
  if (this.image.palette.length === 0) {
3823
3634
  this.obj.data['ColorSpace'] = this.image.colorSpace;
3824
3635
  } else {
3825
- // embed the color palette in the PDF as an object stream
3826
3636
  const palette = this.document.ref();
3827
3637
  palette.end(Buffer.from(this.image.palette));
3828
-
3829
- // build the color space array for the image
3830
3638
  this.obj.data['ColorSpace'] = ['Indexed', 'DeviceRGB', this.image.palette.length / 3 - 1, palette];
3831
3639
  }
3832
-
3833
- // For PNG color types 0, 2 and 3, the transparency data is stored in
3834
- // a dedicated PNG chunk.
3835
3640
  if (this.image.transparency.grayscale != null) {
3836
- // Use Color Key Masking (spec section 4.8.5)
3837
- // An array with N elements, where N is two times the number of color components.
3838
3641
  const val = this.image.transparency.grayscale;
3839
3642
  this.obj.data['Mask'] = [val, val];
3840
3643
  } else if (this.image.transparency.rgb) {
3841
- // Use Color Key Masking (spec section 4.8.5)
3842
- // An array with N elements, where N is two times the number of color components.
3843
3644
  const {
3844
3645
  rgb
3845
3646
  } = this.image.transparency;
@@ -3849,14 +3650,9 @@ class PNGImage {
3849
3650
  }
3850
3651
  this.obj.data['Mask'] = mask;
3851
3652
  } else if (this.image.transparency.indexed) {
3852
- // Create a transparency SMask for the image based on the data
3853
- // in the PLTE and tRNS sections. See below for details on SMasks.
3854
3653
  dataDecoded = true;
3855
3654
  return this.loadIndexedAlphaChannel();
3856
3655
  } else if (hasAlphaChannel) {
3857
- // For PNG color types 4 and 6, the transparency data is stored as a alpha
3858
- // channel mixed in with the main image data. Separate this data out into an
3859
- // SMask object and store it separately in the PDF.
3860
3656
  dataDecoded = true;
3861
3657
  return this.splitAlphaChannel();
3862
3658
  }
@@ -3880,11 +3676,7 @@ class PNGImage {
3880
3676
  sMask.end(this.alphaChannel);
3881
3677
  this.obj.data['SMask'] = sMask;
3882
3678
  }
3883
-
3884
- // add the actual image data
3885
3679
  this.obj.end(this.imgData);
3886
-
3887
- // free memory
3888
3680
  this.image = null;
3889
3681
  return this.imgData = null;
3890
3682
  }
@@ -3897,7 +3689,6 @@ class PNGImage {
3897
3689
  const alphaChannel = Buffer.alloc(pixelCount);
3898
3690
  let i = p = a = 0;
3899
3691
  const len = pixels.length;
3900
- // For 16bit images copy only most significant byte (MSB) - PNG data is always stored in network byte order (MSB first)
3901
3692
  const skipByteCount = this.image.bits === 16 ? 1 : 0;
3902
3693
  while (i < len) {
3903
3694
  for (let colorIndex = 0; colorIndex < colorCount; colorIndex++) {
@@ -3932,10 +3723,6 @@ class PNGImage {
3932
3723
  }
3933
3724
  }
3934
3725
 
3935
- /*
3936
- PDFImage - embeds images in PDF documents
3937
- By Devon Govett
3938
- */
3939
3726
  class PDFImage {
3940
3727
  static open(src, label) {
3941
3728
  let data;
@@ -3967,17 +3754,16 @@ class PDFImage {
3967
3754
  var ImagesMixin = {
3968
3755
  initImages() {
3969
3756
  this._imageRegistry = {};
3970
- return this._imageCount = 0;
3757
+ this._imageCount = 0;
3971
3758
  },
3972
3759
  image(src, x, y, options = {}) {
3973
- let bh, bp, bw, image, ip, left, left1, rotateAngle, originX, originY;
3760
+ let bh, bp, bw, image, ip, left, left1, originX, originY;
3974
3761
  if (typeof x === 'object') {
3975
3762
  options = x;
3976
3763
  x = null;
3977
3764
  }
3978
-
3979
- // Ignore orientation based on document options or image options
3980
3765
  const ignoreOrientation = options.ignoreOrientation || options.ignoreOrientation !== false && this.options.ignoreOrientation;
3766
+ const inDocumentFlow = typeof y !== 'number';
3981
3767
  x = (left = x != null ? x : options.x) != null ? left : this.x;
3982
3768
  y = (left1 = y != null ? y : options.y) != null ? left1 : this.y;
3983
3769
  if (typeof src === 'string') {
@@ -4000,8 +3786,6 @@ var ImagesMixin = {
4000
3786
  width,
4001
3787
  height
4002
3788
  } = image;
4003
-
4004
- // If EXIF orientation calls for it, swap width and height
4005
3789
  if (!ignoreOrientation && image.orientation > 4) {
4006
3790
  [width, height] = [height, width];
4007
3791
  }
@@ -4053,80 +3837,70 @@ var ImagesMixin = {
4053
3837
  y = y + bh - h;
4054
3838
  }
4055
3839
  }
3840
+ let rotateAngle = 0;
3841
+ let xTransform = x;
3842
+ let yTransform = y;
3843
+ let hTransform = h;
3844
+ let wTransform = w;
4056
3845
  if (!ignoreOrientation) {
4057
3846
  switch (image.orientation) {
4058
- // No orientation (need to flip image, though, because of the default transform matrix on the document)
4059
3847
  default:
4060
3848
  case 1:
4061
- h = -h;
4062
- y -= h;
4063
- rotateAngle = 0;
3849
+ hTransform = -h;
3850
+ yTransform += h;
4064
3851
  break;
4065
- // Flip Horizontal
4066
3852
  case 2:
4067
- w = -w;
4068
- h = -h;
4069
- x -= w;
4070
- y -= h;
4071
- rotateAngle = 0;
3853
+ wTransform = -w;
3854
+ hTransform = -h;
3855
+ xTransform += w;
3856
+ yTransform += h;
4072
3857
  break;
4073
- // Rotate 180 degrees
4074
3858
  case 3:
4075
3859
  originX = x;
4076
3860
  originY = y;
4077
- h = -h;
4078
- x -= w;
3861
+ hTransform = -h;
3862
+ xTransform -= w;
4079
3863
  rotateAngle = 180;
4080
3864
  break;
4081
- // Flip vertical
4082
3865
  case 4:
4083
- // Do nothing, image will be flipped
4084
-
4085
3866
  break;
4086
- // Flip horizontally and rotate 270 degrees CW
4087
3867
  case 5:
4088
3868
  originX = x;
4089
3869
  originY = y;
4090
- [w, h] = [h, w];
4091
- y -= h;
3870
+ wTransform = h;
3871
+ hTransform = w;
3872
+ yTransform -= hTransform;
4092
3873
  rotateAngle = 90;
4093
3874
  break;
4094
- // Rotate 90 degrees CW
4095
3875
  case 6:
4096
3876
  originX = x;
4097
3877
  originY = y;
4098
- [w, h] = [h, w];
4099
- h = -h;
3878
+ wTransform = h;
3879
+ hTransform = -w;
4100
3880
  rotateAngle = 90;
4101
3881
  break;
4102
- // Flip horizontally and rotate 90 degrees CW
4103
3882
  case 7:
4104
3883
  originX = x;
4105
3884
  originY = y;
4106
- [w, h] = [h, w];
4107
- h = -h;
4108
- w = -w;
4109
- x -= w;
3885
+ hTransform = -w;
3886
+ wTransform = -h;
3887
+ xTransform += h;
4110
3888
  rotateAngle = 90;
4111
3889
  break;
4112
- // Rotate 270 degrees CW
4113
3890
  case 8:
4114
3891
  originX = x;
4115
3892
  originY = y;
4116
- [w, h] = [h, w];
4117
- h = -h;
4118
- x -= w;
4119
- y -= h;
3893
+ wTransform = h;
3894
+ hTransform = -w;
3895
+ xTransform -= h;
3896
+ yTransform += w;
4120
3897
  rotateAngle = -90;
4121
3898
  break;
4122
3899
  }
4123
3900
  } else {
4124
- h = -h;
4125
- y -= h;
4126
- rotateAngle = 0;
3901
+ hTransform = -h;
3902
+ yTransform += h;
4127
3903
  }
4128
-
4129
- // create link annotations if the link option is given
4130
3904
  if (options.link != null) {
4131
3905
  this.link(x, y, w, h, options.link);
4132
3906
  }
@@ -4136,9 +3910,7 @@ var ImagesMixin = {
4136
3910
  if (options.destination != null) {
4137
3911
  this.addNamedDestination(options.destination, 'XYZ', x, y, null);
4138
3912
  }
4139
-
4140
- // Set the current y position to below the image if it is in the document flow
4141
- if (this.y === y) {
3913
+ if (inDocumentFlow) {
4142
3914
  this.y += h;
4143
3915
  }
4144
3916
  this.save();
@@ -4147,7 +3919,7 @@ var ImagesMixin = {
4147
3919
  origin: [originX, originY]
4148
3920
  });
4149
3921
  }
4150
- this.transform(w, 0, 0, h, x, y);
3922
+ this.transform(wTransform, 0, 0, hTransform, xTransform, yTransform);
4151
3923
  this.addContent(`/${image.label} Do`);
4152
3924
  this.restore();
4153
3925
  return this;
@@ -4173,19 +3945,17 @@ var AnnotationsMixin = {
4173
3945
  options.Rect = this._convertRect(x, y, w, h);
4174
3946
  options.Border = [0, 0, 0];
4175
3947
  if (options.Subtype === 'Link' && typeof options.F === 'undefined') {
4176
- options.F = 1 << 2; // Print Annotation Flag
3948
+ options.F = 1 << 2;
4177
3949
  }
4178
3950
  if (options.Subtype !== 'Link') {
4179
3951
  if (options.C == null) {
4180
3952
  options.C = this._normalizeColor(options.color || [0, 0, 0]);
4181
3953
  }
4182
- } // convert colors
3954
+ }
4183
3955
  delete options.color;
4184
3956
  if (typeof options.Dest === 'string') {
4185
3957
  options.Dest = new String(options.Dest);
4186
3958
  }
4187
-
4188
- // Capitalize keys
4189
3959
  for (let key in options) {
4190
3960
  const val = options[key];
4191
3961
  options[key[0].toUpperCase() + key.slice(1)] = val;
@@ -4198,7 +3968,9 @@ var AnnotationsMixin = {
4198
3968
  note(x, y, w, h, contents, options = {}) {
4199
3969
  options.Subtype = 'Text';
4200
3970
  options.Contents = new String(contents);
4201
- options.Name = 'Comment';
3971
+ if (options.Name == null) {
3972
+ options.Name = 'Comment';
3973
+ }
4202
3974
  if (options.color == null) {
4203
3975
  options.color = [243, 223, 92];
4204
3976
  }
@@ -4216,7 +3988,6 @@ var AnnotationsMixin = {
4216
3988
  link(x, y, w, h, url, options = {}) {
4217
3989
  options.Subtype = 'Link';
4218
3990
  if (typeof url === 'number') {
4219
- // Link to a page in the document (the page must already exist)
4220
3991
  const pages = this._root.data.Pages.data;
4221
3992
  if (url >= 0 && url < pages.Kids.length) {
4222
3993
  options.A = this.ref({
@@ -4228,7 +3999,6 @@ var AnnotationsMixin = {
4228
3999
  throw new Error(`The document has no page ${url}`);
4229
4000
  }
4230
4001
  } else {
4231
- // Link to an external url
4232
4002
  options.A = this.ref({
4233
4003
  S: 'URI',
4234
4004
  URI: new String(url)
@@ -4281,14 +4051,11 @@ var AnnotationsMixin = {
4281
4051
  return this.annotate(x, y, w, h, options);
4282
4052
  },
4283
4053
  fileAnnotation(x, y, w, h, file = {}, options = {}) {
4284
- // create hidden file
4285
4054
  const filespec = this.file(file.src, Object.assign({
4286
4055
  hidden: true
4287
4056
  }, file));
4288
4057
  options.Subtype = 'FileAttachment';
4289
4058
  options.FS = filespec;
4290
-
4291
- // add description from filespec unless description (Contents) has already been set
4292
4059
  if (options.Contents) {
4293
4060
  options.Contents = new String(options.Contents);
4294
4061
  } else if (filespec.data.Desc) {
@@ -4297,14 +4064,9 @@ var AnnotationsMixin = {
4297
4064
  return this.annotate(x, y, w, h, options);
4298
4065
  },
4299
4066
  _convertRect(x1, y1, w, h) {
4300
- // flip y1 and y2
4301
4067
  let y2 = y1;
4302
4068
  y1 += h;
4303
-
4304
- // make x2
4305
4069
  let x2 = x1 + w;
4306
-
4307
- // apply current transformation matrix to points
4308
4070
  const [m0, m1, m2, m3, m4, m5] = this._ctm;
4309
4071
  x1 = m0 * x1 + m2 * y1 + m4;
4310
4072
  y1 = m1 * x1 + m3 * y1 + m5;
@@ -4366,7 +4128,7 @@ class PDFOutline {
4366
4128
 
4367
4129
  var OutlineMixin = {
4368
4130
  initOutline() {
4369
- return this.outline = new PDFOutline(this, null, null, null);
4131
+ this.outline = new PDFOutline(this, null, null, null);
4370
4132
  },
4371
4133
  endOutline() {
4372
4134
  this.outline.endOutline();
@@ -4377,11 +4139,6 @@ var OutlineMixin = {
4377
4139
  }
4378
4140
  };
4379
4141
 
4380
- /*
4381
- PDFStructureContent - a reference to a marked structure content
4382
- By Ben Schmidt
4383
- */
4384
-
4385
4142
  class PDFStructureContent {
4386
4143
  constructor(pageRef, mcid) {
4387
4144
  this.refs = [{
@@ -4394,10 +4151,6 @@ class PDFStructureContent {
4394
4151
  }
4395
4152
  }
4396
4153
 
4397
- /*
4398
- PDFStructureElement - represents an element in the PDF logical structure tree
4399
- By Ben Schmidt
4400
- */
4401
4154
  class PDFStructureElement {
4402
4155
  constructor(document, type, options = {}, children = null) {
4403
4156
  this.document = document;
@@ -4405,7 +4158,6 @@ class PDFStructureElement {
4405
4158
  this._ended = false;
4406
4159
  this._flushed = false;
4407
4160
  this.dictionary = document.ref({
4408
- // Type: "StructElem",
4409
4161
  S: type
4410
4162
  });
4411
4163
  const data = this.dictionary.data;
@@ -4454,7 +4206,6 @@ class PDFStructureElement {
4454
4206
  this._addContentToParentTree(child);
4455
4207
  }
4456
4208
  if (typeof child === 'function' && this._attached) {
4457
- // _contentForClosure() adds the content to the parent tree
4458
4209
  child = this._contentForClosure(child);
4459
4210
  }
4460
4211
  this._children.push(child);
@@ -4530,10 +4281,6 @@ class PDFStructureElement {
4530
4281
  this.dictionary.data.K = [];
4531
4282
  this._children.forEach(child => this._flushChild(child));
4532
4283
  this.dictionary.end();
4533
-
4534
- // free memory used by children; the dictionary itself may still be
4535
- // referenced by a parent structure element or root, but we can
4536
- // at least trim the tree here
4537
4284
  this._children = [];
4538
4285
  this.dictionary.data.K = null;
4539
4286
  this._flushed = true;
@@ -4554,7 +4301,7 @@ class PDFStructureElement {
4554
4301
  this.dictionary.data.K.push(mcid);
4555
4302
  } else {
4556
4303
  this.dictionary.data.K.push({
4557
- Type: "MCR",
4304
+ Type: 'MCR',
4558
4305
  Pg: pageRef,
4559
4306
  MCID: mcid
4560
4307
  });
@@ -4564,25 +4311,18 @@ class PDFStructureElement {
4564
4311
  }
4565
4312
  }
4566
4313
 
4567
- /*
4568
- PDFNumberTree - represents a number tree object
4569
- */
4570
4314
  class PDFNumberTree extends PDFTree {
4571
4315
  _compareKeys(a, b) {
4572
4316
  return parseInt(a) - parseInt(b);
4573
4317
  }
4574
4318
  _keysName() {
4575
- return "Nums";
4319
+ return 'Nums';
4576
4320
  }
4577
4321
  _dataForKey(k) {
4578
4322
  return parseInt(k);
4579
4323
  }
4580
4324
  }
4581
4325
 
4582
- /*
4583
- Markings mixin - support marked content sequences in content streams
4584
- By Ben Schmidt
4585
- */
4586
4326
  var MarkingsMixin = {
4587
4327
  initMarkings(options) {
4588
4328
  this.structChildren = [];
@@ -4701,6 +4441,9 @@ var MarkingsMixin = {
4701
4441
  }
4702
4442
  return this._root.data.MarkInfo;
4703
4443
  },
4444
+ hasMarkInfoDictionary() {
4445
+ return !!this._root.data.MarkInfo;
4446
+ },
4704
4447
  getStructTreeRoot() {
4705
4448
  if (!this._root.data.StructTreeRoot) {
4706
4449
  this._root.data.StructTreeRoot = this.ref({
@@ -4715,7 +4458,6 @@ var MarkingsMixin = {
4715
4458
  return this.getStructTreeRoot().data.ParentTree;
4716
4459
  },
4717
4460
  createStructParentTreeNextKey() {
4718
- // initialise the MarkInfo dictionary
4719
4461
  this.getMarkInfoDictionary();
4720
4462
  const structTreeRoot = this.getStructTreeRoot();
4721
4463
  const key = structTreeRoot.data.ParentTreeNextKey++;
@@ -4779,10 +4521,6 @@ const FORMAT_DEFAULT = {
4779
4521
  }
4780
4522
  };
4781
4523
  var AcroFormMixin = {
4782
- /**
4783
- * Must call if adding AcroForms to a document. Must also call font() before
4784
- * this method to set the default font.
4785
- */
4786
4524
  initForm() {
4787
4525
  if (!this._font) {
4788
4526
  throw new Error('Must set a font before calling initForm method');
@@ -4805,9 +4543,6 @@ var AcroFormMixin = {
4805
4543
  this._root.data.AcroForm = AcroForm;
4806
4544
  return this;
4807
4545
  },
4808
- /**
4809
- * Called automatically by document.js
4810
- */
4811
4546
  endAcroForm() {
4812
4547
  if (this._root.data.AcroForm) {
4813
4548
  if (!Object.keys(this._acroform.fonts).length && !this._acroform.defaultFont) {
@@ -4833,38 +4568,18 @@ var AcroFormMixin = {
4833
4568
  }
4834
4569
  return this;
4835
4570
  },
4836
- /**
4837
- * Creates and adds a form field to the document. Form fields are intermediate
4838
- * nodes in a PDF form that are used to specify form name heirarchy and form
4839
- * value defaults.
4840
- * @param {string} name - field name (T attribute in field dictionary)
4841
- * @param {object} options - other attributes to include in field dictionary
4842
- */
4843
4571
  formField(name, options = {}) {
4844
4572
  let fieldDict = this._fieldDict(name, null, options);
4845
4573
  let fieldRef = this.ref(fieldDict);
4846
4574
  this._addToParent(fieldRef);
4847
4575
  return fieldRef;
4848
4576
  },
4849
- /**
4850
- * Creates and adds a Form Annotation to the document. Form annotations are
4851
- * called Widget annotations internally within a PDF file.
4852
- * @param {string} name - form field name (T attribute of widget annotation
4853
- * dictionary)
4854
- * @param {number} x
4855
- * @param {number} y
4856
- * @param {number} w
4857
- * @param {number} h
4858
- * @param {object} options
4859
- */
4860
4577
  formAnnotation(name, type, x, y, w, h, options = {}) {
4861
4578
  let fieldDict = this._fieldDict(name, type, options);
4862
4579
  fieldDict.Subtype = 'Widget';
4863
4580
  if (fieldDict.F === undefined) {
4864
- fieldDict.F = 4; // print the annotation
4581
+ fieldDict.F = 4;
4865
4582
  }
4866
-
4867
- // Add Field annot to page, and get it's ref
4868
4583
  this.annotate(x, y, w, h, fieldDict);
4869
4584
  let annotRef = this.page.annotations[this.page.annotations.length - 1];
4870
4585
  return this._addToParent(annotRef);
@@ -5025,23 +4740,18 @@ var AcroFormMixin = {
5025
4740
  delete options.align;
5026
4741
  }
5027
4742
  if (result !== 0) {
5028
- options.Q = result; // default
4743
+ options.Q = result;
5029
4744
  }
5030
4745
  return options;
5031
4746
  },
5032
4747
  _resolveFont(options) {
5033
- // add current font to document-level AcroForm dict if necessary
5034
4748
  if (this._acroform.fonts[this._font.id] == null) {
5035
4749
  this._acroform.fonts[this._font.id] = this._font.ref();
5036
4750
  }
5037
-
5038
- // add current font to field's resource dict (RD) if not the default acroform font
5039
4751
  if (this._acroform.defaultFont !== this._font.name) {
5040
4752
  options.DR = {
5041
4753
  Font: {}
5042
4754
  };
5043
-
5044
- // Get the fontSize option. If not set use auto sizing
5045
4755
  const fontSize = options.fontSize || 0;
5046
4756
  options.DR.Font[this._font.id] = this._font.ref();
5047
4757
  options.DA = new String(`/${this._font.id} ${fontSize} Tf 0 g`);
@@ -5093,19 +4803,6 @@ var AcroFormMixin = {
5093
4803
  };
5094
4804
 
5095
4805
  var AttachmentsMixin = {
5096
- /**
5097
- * Embed contents of `src` in PDF
5098
- * @param {Buffer | ArrayBuffer | string} src input Buffer, ArrayBuffer, base64 encoded string or path to file
5099
- * @param {object} options
5100
- * * options.name: filename to be shown in PDF, will use `src` if none set
5101
- * * options.type: filetype to be shown in PDF
5102
- * * options.description: description to be shown in PDF
5103
- * * options.hidden: if true, do not add attachment to EmbeddedFiles dictionary. Useful for file attachment annotations
5104
- * * options.creationDate: override creation date
5105
- * * options.modifiedDate: override modified date
5106
- * * options.relationship: Relationship between the PDF document and its attached file. Can be 'Alternative', 'Data', 'Source', 'Supplement' or 'Unspecified'.
5107
- * @returns filespec reference
5108
- */
5109
4806
  file(src, options = {}) {
5110
4807
  options.name = options.name || src;
5111
4808
  options.relationship = options.relationship || 'Unspecified';
@@ -5133,8 +4830,6 @@ var AttachmentsMixin = {
5133
4830
  if (!data) {
5134
4831
  throw new Error(`Could not read contents of file at filepath ${src}`);
5135
4832
  }
5136
-
5137
- // update CreationDate and ModDate
5138
4833
  const {
5139
4834
  birthtime,
5140
4835
  ctime
@@ -5143,26 +4838,18 @@ var AttachmentsMixin = {
5143
4838
  refBody.Params.ModDate = ctime;
5144
4839
  }
5145
4840
  }
5146
-
5147
- // override creation date and modified date
5148
4841
  if (options.creationDate instanceof Date) {
5149
4842
  refBody.Params.CreationDate = options.creationDate;
5150
4843
  }
5151
4844
  if (options.modifiedDate instanceof Date) {
5152
4845
  refBody.Params.ModDate = options.modifiedDate;
5153
4846
  }
5154
- // add optional subtype
5155
4847
  if (options.type) {
5156
4848
  refBody.Subtype = options.type.replace('/', '#2F');
5157
4849
  }
5158
-
5159
- // add checksum and size information
5160
4850
  const checksum = CryptoJS.MD5(CryptoJS.lib.WordArray.create(new Uint8Array(data)));
5161
4851
  refBody.Params.CheckSum = new String(checksum);
5162
4852
  refBody.Params.Size = data.byteLength;
5163
-
5164
- // save some space when embedding the same file again
5165
- // if a file with the same name and metadata exists, reuse its reference
5166
4853
  let ref;
5167
4854
  if (!this._fileRegistry) this._fileRegistry = {};
5168
4855
  let file = this._fileRegistry[options.name];
@@ -5176,7 +4863,6 @@ var AttachmentsMixin = {
5176
4863
  ref
5177
4864
  };
5178
4865
  }
5179
- // add filespec for embedded file
5180
4866
  const fileSpecBody = {
5181
4867
  Type: 'Filespec',
5182
4868
  AFRelationship: options.relationship,
@@ -5194,8 +4880,6 @@ var AttachmentsMixin = {
5194
4880
  if (!options.hidden) {
5195
4881
  this.addNamedEmbeddedFile(options.name, filespec);
5196
4882
  }
5197
-
5198
- // Add file to the catalogue to be PDF/A3 compliant
5199
4883
  if (this._root.data.AF) {
5200
4884
  this._root.data.AF.push(filespec);
5201
4885
  } else {
@@ -5204,8 +4888,6 @@ var AttachmentsMixin = {
5204
4888
  return filespec;
5205
4889
  }
5206
4890
  };
5207
-
5208
- /** check two embedded file metadata objects for equality */
5209
4891
  function isEqual(a, b) {
5210
4892
  return a.Subtype === b.Subtype && a.Params.CheckSum.toString() === b.Params.CheckSum.toString() && a.Params.Size === b.Params.Size && a.Params.CreationDate.getTime() === b.Params.CreationDate.getTime() && (a.Params.ModDate === undefined && b.Params.ModDate === undefined || a.Params.ModDate.getTime() === b.Params.ModDate.getTime());
5211
4893
  }
@@ -5216,7 +4898,6 @@ var PDFA = {
5216
4898
  this.subset_conformance = pSubset.charAt(pSubset.length - 1).toUpperCase();
5217
4899
  this.subset = parseInt(pSubset.charAt(pSubset.length - 2));
5218
4900
  } else {
5219
- // Default to Basic conformance when user doesn't specify
5220
4901
  this.subset_conformance = 'B';
5221
4902
  this.subset = parseInt(pSubset.charAt(pSubset.length - 1));
5222
4903
  }
@@ -5301,6 +4982,642 @@ var SubsetMixin = {
5301
4982
  }
5302
4983
  };
5303
4984
 
4985
+ const ROW_FIELDS = ['height', 'minHeight', 'maxHeight'];
4986
+ const COLUMN_FIELDS = ['width', 'minWidth', 'maxWidth'];
4987
+ function memoize(fn, maxSize) {
4988
+ const cache = new Map();
4989
+ return function (...args) {
4990
+ const key = args[0];
4991
+ if (!cache.has(key)) {
4992
+ cache.set(key, fn(...args));
4993
+ if (cache.size > maxSize) cache.delete(cache.keys().next());
4994
+ }
4995
+ return cache.get(key);
4996
+ };
4997
+ }
4998
+ function isObject(item) {
4999
+ return item && typeof item === 'object' && !Array.isArray(item);
5000
+ }
5001
+ function deepMerge(target, ...sources) {
5002
+ if (!isObject(target)) return target;
5003
+ target = deepClone(target);
5004
+ for (const source of sources) {
5005
+ if (isObject(source)) {
5006
+ for (const key in source) {
5007
+ if (isObject(source[key])) {
5008
+ if (!(key in target)) target[key] = {};
5009
+ target[key] = deepMerge(target[key], source[key]);
5010
+ } else if (source[key] !== undefined) {
5011
+ target[key] = deepClone(source[key]);
5012
+ }
5013
+ }
5014
+ }
5015
+ }
5016
+ return target;
5017
+ }
5018
+ function deepClone(obj) {
5019
+ let result = obj;
5020
+ if (typeof obj == 'object') {
5021
+ result = Array.isArray(obj) ? [] : {};
5022
+ for (const key in obj) result[key] = deepClone(obj[key]);
5023
+ }
5024
+ return result;
5025
+ }
5026
+
5027
+ function normalizedDefaultStyle(defaultStyleInternal) {
5028
+ let defaultStyle = defaultStyleInternal;
5029
+ if (typeof defaultStyle !== 'object') defaultStyle = {
5030
+ text: defaultStyle
5031
+ };
5032
+ const defaultRowStyle = Object.fromEntries(Object.entries(defaultStyle).filter(([k]) => ROW_FIELDS.includes(k)));
5033
+ const defaultColStyle = Object.fromEntries(Object.entries(defaultStyle).filter(([k]) => COLUMN_FIELDS.includes(k)));
5034
+ defaultStyle.padding = normalizeSides(defaultStyle.padding);
5035
+ defaultStyle.border = normalizeSides(defaultStyle.border);
5036
+ defaultStyle.borderColor = normalizeSides(defaultStyle.borderColor);
5037
+ defaultStyle.align = normalizeAlignment(defaultStyle.align);
5038
+ return {
5039
+ defaultStyle,
5040
+ defaultRowStyle,
5041
+ defaultColStyle
5042
+ };
5043
+ }
5044
+ function normalizedRowStyle(defaultRowStyle, rowStyleInternal, i) {
5045
+ let rowStyle = rowStyleInternal(i);
5046
+ if (rowStyle == null || typeof rowStyle !== 'object') {
5047
+ rowStyle = {
5048
+ height: rowStyle
5049
+ };
5050
+ }
5051
+ rowStyle.padding = normalizeSides(rowStyle.padding);
5052
+ rowStyle.border = normalizeSides(rowStyle.border);
5053
+ rowStyle.borderColor = normalizeSides(rowStyle.borderColor);
5054
+ rowStyle.align = normalizeAlignment(rowStyle.align);
5055
+ rowStyle = deepMerge(defaultRowStyle, rowStyle);
5056
+ const document = this.document;
5057
+ const page = document.page;
5058
+ const contentHeight = page.contentHeight;
5059
+ if (rowStyle.height == null || rowStyle.height === 'auto') {
5060
+ rowStyle.height = 'auto';
5061
+ } else {
5062
+ rowStyle.height = document.sizeToPoint(rowStyle.height, 0, page, contentHeight);
5063
+ }
5064
+ rowStyle.minHeight = document.sizeToPoint(rowStyle.minHeight, 0, page, contentHeight);
5065
+ rowStyle.maxHeight = document.sizeToPoint(rowStyle.maxHeight, 0, page, contentHeight);
5066
+ return rowStyle;
5067
+ }
5068
+ function normalizedColumnStyle(defaultColStyle, colStyleInternal, i) {
5069
+ let colStyle = colStyleInternal(i);
5070
+ if (colStyle == null || typeof colStyle !== 'object') {
5071
+ colStyle = {
5072
+ width: colStyle
5073
+ };
5074
+ }
5075
+ colStyle.padding = normalizeSides(colStyle.padding);
5076
+ colStyle.border = normalizeSides(colStyle.border);
5077
+ colStyle.borderColor = normalizeSides(colStyle.borderColor);
5078
+ colStyle.align = normalizeAlignment(colStyle.align);
5079
+ colStyle = deepMerge(defaultColStyle, colStyle);
5080
+ if (colStyle.width == null || colStyle.width === '*') {
5081
+ colStyle.width = '*';
5082
+ } else {
5083
+ colStyle.width = this.document.sizeToPoint(colStyle.width, 0, this.document.page, this._maxWidth);
5084
+ }
5085
+ colStyle.minWidth = this.document.sizeToPoint(colStyle.minWidth, 0, this.document.page, this._maxWidth);
5086
+ colStyle.maxWidth = this.document.sizeToPoint(colStyle.maxWidth, 0, this.document.page, this._maxWidth);
5087
+ return colStyle;
5088
+ }
5089
+ function normalizeAlignment(align) {
5090
+ return align == null || typeof align === 'string' ? {
5091
+ x: align,
5092
+ y: align
5093
+ } : align;
5094
+ }
5095
+
5096
+ function normalizeTable() {
5097
+ const doc = this.document;
5098
+ const opts = this.opts;
5099
+ let index = doc._tableIndex++;
5100
+ this._id = new String(opts.id ?? `table-${index}`);
5101
+ this._position = {
5102
+ x: doc.sizeToPoint(opts.position?.x, doc.x),
5103
+ y: doc.sizeToPoint(opts.position?.y, doc.y)
5104
+ };
5105
+ this._maxWidth = doc.sizeToPoint(opts.maxWidth, doc.page.width - doc.page.margins.right - this._position.x);
5106
+ const {
5107
+ defaultStyle,
5108
+ defaultColStyle,
5109
+ defaultRowStyle
5110
+ } = normalizedDefaultStyle(opts.defaultStyle);
5111
+ this._defaultStyle = defaultStyle;
5112
+ let colStyle;
5113
+ if (opts.columnStyles) {
5114
+ if (Array.isArray(opts.columnStyles)) {
5115
+ colStyle = i => opts.columnStyles[i];
5116
+ } else if (typeof opts.columnStyles === 'function') {
5117
+ colStyle = memoize(i => opts.columnStyles(i), Infinity);
5118
+ } else if (typeof opts.columnStyles === 'object') {
5119
+ colStyle = () => opts.columnStyles;
5120
+ }
5121
+ }
5122
+ if (!colStyle) colStyle = () => ({});
5123
+ this._colStyle = normalizedColumnStyle.bind(this, defaultColStyle, colStyle);
5124
+ let rowStyle;
5125
+ if (opts.rowStyles) {
5126
+ if (Array.isArray(opts.rowStyles)) {
5127
+ rowStyle = i => opts.rowStyles[i];
5128
+ } else if (typeof opts.rowStyles === 'function') {
5129
+ rowStyle = memoize(i => opts.rowStyles(i), 10);
5130
+ } else if (typeof opts.rowStyles === 'object') {
5131
+ rowStyle = () => opts.rowStyles;
5132
+ }
5133
+ }
5134
+ if (!rowStyle) rowStyle = () => ({});
5135
+ this._rowStyle = normalizedRowStyle.bind(this, defaultRowStyle, rowStyle);
5136
+ }
5137
+ function normalizeText(text) {
5138
+ if (text != null) text = `${text}`;
5139
+ return text;
5140
+ }
5141
+ function normalizeCell(cell, rowIndex, colIndex) {
5142
+ const colStyle = this._colStyle(colIndex);
5143
+ let rowStyle = this._rowStyle(rowIndex);
5144
+ const font = deepMerge({}, colStyle.font, rowStyle.font, cell.font);
5145
+ const customFont = Object.values(font).filter(v => v != null).length > 0;
5146
+ const doc = this.document;
5147
+ const rollbackFont = doc._fontSource;
5148
+ const rollbackFontSize = doc._fontSize;
5149
+ const rollbackFontFamily = doc._fontFamily;
5150
+ if (customFont) {
5151
+ if (font.src) doc.font(font.src, font.family);
5152
+ if (font.size) doc.fontSize(font.size);
5153
+ rowStyle = this._rowStyle(rowIndex);
5154
+ }
5155
+ cell.padding = normalizeSides(cell.padding);
5156
+ cell.border = normalizeSides(cell.border);
5157
+ cell.borderColor = normalizeSides(cell.borderColor);
5158
+ const config = deepMerge(this._defaultStyle, colStyle, rowStyle, cell);
5159
+ config.rowIndex = rowIndex;
5160
+ config.colIndex = colIndex;
5161
+ config.font = font ?? {};
5162
+ config.customFont = customFont;
5163
+ config.text = normalizeText(config.text);
5164
+ config.rowSpan = config.rowSpan ?? 1;
5165
+ config.colSpan = config.colSpan ?? 1;
5166
+ config.padding = normalizeSides(config.padding, '0.25em', x => doc.sizeToPoint(x, '0.25em'));
5167
+ config.border = normalizeSides(config.border, 1, x => doc.sizeToPoint(x, 1));
5168
+ config.borderColor = normalizeSides(config.borderColor, 'black', x => x ?? 'black');
5169
+ config.align = normalizeAlignment(config.align);
5170
+ config.align.x = config.align.x ?? 'left';
5171
+ config.align.y = config.align.y ?? 'top';
5172
+ config.textStroke = doc.sizeToPoint(config.textStroke, 0);
5173
+ config.textStrokeColor = config.textStrokeColor ?? 'black';
5174
+ config.textColor = config.textColor ?? 'black';
5175
+ config.textOptions = config.textOptions ?? {};
5176
+ config.id = new String(config.id ?? `${this._id}-${rowIndex}-${colIndex}`);
5177
+ config.type = config.type?.toUpperCase() === 'TH' ? 'TH' : 'TD';
5178
+ if (config.scope) {
5179
+ config.scope = config.scope.toLowerCase();
5180
+ if (config.scope === 'row') config.scope = 'Row';else if (config.scope === 'both') config.scope = 'Both';else if (config.scope === 'column') config.scope = 'Column';
5181
+ }
5182
+ if (typeof this.opts.debug === 'boolean') config.debug = this.opts.debug;
5183
+ if (customFont) doc.font(rollbackFont, rollbackFontFamily, rollbackFontSize);
5184
+ return config;
5185
+ }
5186
+ function normalizeRow(row, rowIndex) {
5187
+ if (!this._cellClaim) this._cellClaim = new Set();
5188
+ let colIndex = 0;
5189
+ return row.map(cell => {
5190
+ if (cell == null || typeof cell !== 'object') cell = {
5191
+ text: cell
5192
+ };
5193
+ while (this._cellClaim.has(`${rowIndex},${colIndex}`)) {
5194
+ colIndex++;
5195
+ }
5196
+ cell = normalizeCell.call(this, cell, rowIndex, colIndex);
5197
+ for (let i = 0; i < cell.rowSpan; i++) {
5198
+ for (let j = 0; j < cell.colSpan; j++) {
5199
+ this._cellClaim.add(`${rowIndex + i},${colIndex + j}`);
5200
+ }
5201
+ }
5202
+ colIndex += cell.colSpan;
5203
+ return cell;
5204
+ });
5205
+ }
5206
+
5207
+ function ensure(row) {
5208
+ this._columnWidths = [];
5209
+ ensureColumnWidths.call(this, row.reduce((a, cell) => a + cell.colSpan, 0));
5210
+ this._rowHeights = [];
5211
+ this._rowYPos = [this._position.y];
5212
+ this._rowBuffer = new Set();
5213
+ }
5214
+ function ensureColumnWidths(numCols) {
5215
+ let starColumnIndexes = [];
5216
+ let starMinAcc = 0;
5217
+ let unclaimedWidth = this._maxWidth;
5218
+ for (let i = 0; i < numCols; i++) {
5219
+ let col = this._colStyle(i);
5220
+ if (col.width === '*') {
5221
+ starColumnIndexes[i] = col;
5222
+ starMinAcc += col.minWidth;
5223
+ } else {
5224
+ unclaimedWidth -= col.width;
5225
+ this._columnWidths[i] = col.width;
5226
+ }
5227
+ }
5228
+ let starColCount = starColumnIndexes.reduce(x => x + 1, 0);
5229
+ if (starMinAcc >= unclaimedWidth) {
5230
+ starColumnIndexes.forEach((cell, i) => {
5231
+ this._columnWidths[i] = cell.minWidth;
5232
+ });
5233
+ } else if (starColCount > 0) {
5234
+ starColumnIndexes.forEach((col, i) => {
5235
+ let starSize = unclaimedWidth / starColCount;
5236
+ this._columnWidths[i] = Math.max(starSize, col.minWidth);
5237
+ if (col.maxWidth > 0) {
5238
+ this._columnWidths[i] = Math.min(this._columnWidths[i], col.maxWidth);
5239
+ }
5240
+ unclaimedWidth -= this._columnWidths[i];
5241
+ starColCount--;
5242
+ });
5243
+ }
5244
+ let tempX = this._position.x;
5245
+ this._columnXPos = Array.from(this._columnWidths, v => {
5246
+ const t = tempX;
5247
+ tempX += v;
5248
+ return t;
5249
+ });
5250
+ }
5251
+ function measure(row, rowIndex) {
5252
+ row.forEach(cell => this._rowBuffer.add(cell));
5253
+ if (rowIndex > 0) {
5254
+ this._rowYPos[rowIndex] = this._rowYPos[rowIndex - 1] + this._rowHeights[rowIndex - 1];
5255
+ }
5256
+ const rowStyle = this._rowStyle(rowIndex);
5257
+ let toRender = [];
5258
+ this._rowBuffer.forEach(cell => {
5259
+ if (cell.rowIndex + cell.rowSpan - 1 === rowIndex) {
5260
+ toRender.push(measureCell.call(this, cell, rowStyle.height));
5261
+ this._rowBuffer.delete(cell);
5262
+ }
5263
+ });
5264
+ let rowHeight = rowStyle.height;
5265
+ if (rowHeight === 'auto') {
5266
+ rowHeight = toRender.reduce((acc, cell) => {
5267
+ let minHeight = cell.textBounds.height + cell.padding.top + cell.padding.bottom;
5268
+ for (let i = 0; i < cell.rowSpan - 1; i++) {
5269
+ minHeight -= this._rowHeights[cell.rowIndex + i];
5270
+ }
5271
+ return Math.max(acc, minHeight);
5272
+ }, 0);
5273
+ }
5274
+ rowHeight = Math.max(rowHeight, rowStyle.minHeight);
5275
+ if (rowStyle.maxHeight > 0) {
5276
+ rowHeight = Math.min(rowHeight, rowStyle.maxHeight);
5277
+ }
5278
+ this._rowHeights[rowIndex] = rowHeight;
5279
+ let newPage = false;
5280
+ if (rowHeight > this.document.page.contentHeight) {
5281
+ console.warn(new Error(`Row ${rowIndex} requested more than the safe page height, row has been clamped`).stack.slice(7));
5282
+ this._rowHeights[rowIndex] = this.document.page.maxY() - this._rowYPos[rowIndex];
5283
+ } else if (this._rowYPos[rowIndex] + rowHeight >= this.document.page.maxY()) {
5284
+ this._rowYPos[rowIndex] = this.document.page.margins.top;
5285
+ newPage = true;
5286
+ }
5287
+ return {
5288
+ newPage,
5289
+ toRender: toRender.map(cell => measureCell.call(this, cell, rowHeight))
5290
+ };
5291
+ }
5292
+ function measureCell(cell, rowHeight) {
5293
+ let cellWidth = 0;
5294
+ for (let i = 0; i < cell.colSpan; i++) {
5295
+ cellWidth += this._columnWidths[cell.colIndex + i];
5296
+ }
5297
+ let cellHeight = rowHeight;
5298
+ if (cellHeight === 'auto') {
5299
+ cellHeight = this.document.page.contentHeight;
5300
+ } else {
5301
+ for (let i = 0; i < cell.rowSpan - 1; i++) {
5302
+ cellHeight += this._rowHeights[cell.rowIndex + i];
5303
+ }
5304
+ }
5305
+ const textAllocatedWidth = cellWidth - cell.padding.left - cell.padding.right;
5306
+ const textAllocatedHeight = cellHeight - cell.padding.top - cell.padding.bottom;
5307
+ const rotation = cell.textOptions.rotation ?? 0;
5308
+ const {
5309
+ width: textMaxWidth,
5310
+ height: textMaxHeight
5311
+ } = computeBounds(rotation, textAllocatedWidth, textAllocatedHeight);
5312
+ const textOptions = {
5313
+ align: cell.align.x,
5314
+ ellipsis: true,
5315
+ stroke: cell.textStroke > 0,
5316
+ fill: true,
5317
+ width: textMaxWidth,
5318
+ height: textMaxHeight,
5319
+ rotation,
5320
+ ...cell.textOptions
5321
+ };
5322
+ let textBounds = {
5323
+ x: 0,
5324
+ y: 0,
5325
+ width: 0,
5326
+ height: 0
5327
+ };
5328
+ if (cell.text) {
5329
+ const rollbackFont = this.document._fontSource;
5330
+ const rollbackFontSize = this.document._fontSize;
5331
+ const rollbackFontFamily = this.document._fontFamily;
5332
+ if (cell.font?.src) this.document.font(cell.font.src, cell.font?.family);
5333
+ if (cell.font?.size) this.document.fontSize(cell.font.size);
5334
+ const unRotatedTextBounds = this.document.boundsOfString(cell.text, 0, 0, {
5335
+ ...textOptions,
5336
+ rotation: 0
5337
+ });
5338
+ textOptions.width = unRotatedTextBounds.width;
5339
+ textOptions.height = unRotatedTextBounds.height;
5340
+ textBounds = this.document.boundsOfString(cell.text, 0, 0, textOptions);
5341
+ this.document.font(rollbackFont, rollbackFontFamily, rollbackFontSize);
5342
+ }
5343
+ return {
5344
+ ...cell,
5345
+ textOptions,
5346
+ x: this._columnXPos[cell.colIndex],
5347
+ y: this._rowYPos[cell.rowIndex],
5348
+ textX: this._columnXPos[cell.colIndex] + cell.padding.left,
5349
+ textY: this._rowYPos[cell.rowIndex] + cell.padding.top,
5350
+ width: cellWidth,
5351
+ height: cellHeight,
5352
+ textAllocatedHeight,
5353
+ textAllocatedWidth,
5354
+ textBounds
5355
+ };
5356
+ }
5357
+ function computeBounds(rotation, allocWidth, allocHeight) {
5358
+ let textMaxWidth, textMaxHeight;
5359
+ const cos = cosine(rotation);
5360
+ const sin = sine(rotation);
5361
+ if (rotation === 0 || rotation === 180) {
5362
+ textMaxWidth = allocWidth;
5363
+ textMaxHeight = allocHeight;
5364
+ } else if (rotation === 90 || rotation === 270) {
5365
+ textMaxWidth = allocHeight;
5366
+ textMaxHeight = allocWidth;
5367
+ } else if (rotation < 90 || rotation > 180 && rotation < 270) {
5368
+ textMaxWidth = allocWidth / (2 * cos);
5369
+ textMaxHeight = allocWidth / (2 * sin);
5370
+ } else {
5371
+ textMaxHeight = allocWidth / (2 * cos);
5372
+ textMaxWidth = allocWidth / (2 * sin);
5373
+ }
5374
+ const EF = sin * textMaxWidth;
5375
+ const FG = cos * textMaxHeight;
5376
+ if (EF + FG > allocHeight) {
5377
+ const denominator = cos * cos - sin * sin;
5378
+ if (rotation === 0 || rotation === 180) {
5379
+ textMaxWidth = allocWidth;
5380
+ textMaxHeight = allocHeight;
5381
+ } else if (rotation === 90 || rotation === 270) {
5382
+ textMaxWidth = allocHeight;
5383
+ textMaxHeight = allocWidth;
5384
+ } else if (rotation < 90 || rotation > 180 && rotation < 270) {
5385
+ textMaxWidth = (allocWidth * cos - allocHeight * sin) / denominator;
5386
+ textMaxHeight = (allocHeight * cos - allocWidth * sin) / denominator;
5387
+ } else {
5388
+ textMaxHeight = (allocWidth * cos - allocHeight * sin) / denominator;
5389
+ textMaxWidth = (allocHeight * cos - allocWidth * sin) / denominator;
5390
+ }
5391
+ }
5392
+ return {
5393
+ width: Math.abs(textMaxWidth),
5394
+ height: Math.abs(textMaxHeight)
5395
+ };
5396
+ }
5397
+
5398
+ function accommodateTable() {
5399
+ const structParent = this.opts.structParent;
5400
+ if (structParent) {
5401
+ this._tableStruct = this.document.struct('Table');
5402
+ this._tableStruct.dictionary.data.ID = this._id;
5403
+ if (structParent instanceof PDFStructureElement) {
5404
+ structParent.add(this._tableStruct);
5405
+ } else if (structParent instanceof PDFDocument) {
5406
+ structParent.addStructure(this._tableStruct);
5407
+ }
5408
+ this._headerRowLookup = {};
5409
+ this._headerColumnLookup = {};
5410
+ }
5411
+ }
5412
+ function accommodateCleanup() {
5413
+ if (this._tableStruct) this._tableStruct.end();
5414
+ }
5415
+ function accessibleRow(row, rowIndex, renderCell) {
5416
+ const rowStruct = this.document.struct('TR');
5417
+ rowStruct.dictionary.data.ID = new String(`${this._id}-${rowIndex}`);
5418
+ this._tableStruct.add(rowStruct);
5419
+ row.forEach(cell => renderCell(cell, rowStruct));
5420
+ rowStruct.end();
5421
+ }
5422
+ function accessibleCell(cell, rowStruct, callback) {
5423
+ const doc = this.document;
5424
+ const cellStruct = doc.struct(cell.type, {
5425
+ title: cell.title
5426
+ });
5427
+ cellStruct.dictionary.data.ID = cell.id;
5428
+ rowStruct.add(cellStruct);
5429
+ const padding = cell.padding;
5430
+ const border = cell.border;
5431
+ const attributes = {
5432
+ O: 'Table',
5433
+ Width: cell.width,
5434
+ Height: cell.height,
5435
+ Padding: [padding.top, padding.bottom, padding.left, padding.right],
5436
+ RowSpan: cell.rowSpan > 1 ? cell.rowSpan : undefined,
5437
+ ColSpan: cell.colSpan > 1 ? cell.colSpan : undefined,
5438
+ BorderThickness: [border.top, border.bottom, border.left, border.right]
5439
+ };
5440
+ if (cell.type === 'TH') {
5441
+ if (cell.scope === 'Row' || cell.scope === 'Both') {
5442
+ for (let i = 0; i < cell.rowSpan; i++) {
5443
+ if (!this._headerRowLookup[cell.rowIndex + i]) {
5444
+ this._headerRowLookup[cell.rowIndex + i] = [];
5445
+ }
5446
+ this._headerRowLookup[cell.rowIndex + i].push(cell.id);
5447
+ }
5448
+ attributes.Scope = cell.scope;
5449
+ }
5450
+ if (cell.scope === 'Column' || cell.scope === 'Both') {
5451
+ for (let i = 0; i < cell.colSpan; i++) {
5452
+ if (!this._headerColumnLookup[cell.colIndex + i]) {
5453
+ this._headerColumnLookup[cell.colIndex + i] = [];
5454
+ }
5455
+ this._headerColumnLookup[cell.colIndex + i].push(cell.id);
5456
+ }
5457
+ attributes.Scope = cell.scope;
5458
+ }
5459
+ }
5460
+ const Headers = new Set([...Array.from({
5461
+ length: cell.colSpan
5462
+ }, (_, i) => this._headerColumnLookup[cell.colIndex + i]).flat(), ...Array.from({
5463
+ length: cell.rowSpan
5464
+ }, (_, i) => this._headerRowLookup[cell.rowIndex + i]).flat()].filter(Boolean));
5465
+ if (Headers.size) attributes.Headers = Array.from(Headers);
5466
+ const normalizeColor = doc._normalizeColor;
5467
+ if (cell.backgroundColor != null) {
5468
+ attributes.BackgroundColor = normalizeColor(cell.backgroundColor);
5469
+ }
5470
+ const hasBorder = [border.top, border.bottom, border.left, border.right];
5471
+ if (hasBorder.some(x => x)) {
5472
+ const borderColor = cell.borderColor;
5473
+ attributes.BorderColor = [hasBorder[0] ? normalizeColor(borderColor.top) : null, hasBorder[1] ? normalizeColor(borderColor.bottom) : null, hasBorder[2] ? normalizeColor(borderColor.left) : null, hasBorder[3] ? normalizeColor(borderColor.right) : null];
5474
+ }
5475
+ Object.keys(attributes).forEach(key => attributes[key] === undefined && delete attributes[key]);
5476
+ cellStruct.dictionary.data.A = doc.ref(attributes);
5477
+ cellStruct.add(callback);
5478
+ cellStruct.end();
5479
+ cellStruct.dictionary.data.A.end();
5480
+ }
5481
+
5482
+ function renderRow(row, rowIndex) {
5483
+ if (this._tableStruct) {
5484
+ accessibleRow.call(this, row, rowIndex, renderCell.bind(this));
5485
+ } else {
5486
+ row.forEach(cell => renderCell.call(this, cell));
5487
+ }
5488
+ return this._rowYPos[rowIndex] + this._rowHeights[rowIndex];
5489
+ }
5490
+ function renderCell(cell, rowStruct) {
5491
+ const cellRenderer = () => {
5492
+ if (cell.backgroundColor != null) {
5493
+ this.document.save().rect(cell.x, cell.y, cell.width, cell.height).fill(cell.backgroundColor).restore();
5494
+ }
5495
+ renderBorder.call(this, cell.border, cell.borderColor, cell.x, cell.y, cell.width, cell.height);
5496
+ if (cell.debug) {
5497
+ this.document.save();
5498
+ this.document.dash(1, {
5499
+ space: 1
5500
+ }).lineWidth(1).strokeOpacity(0.3);
5501
+ this.document.rect(cell.x, cell.y, cell.width, cell.height).stroke('green');
5502
+ this.document.restore();
5503
+ }
5504
+ if (cell.text) renderCellText.call(this, cell);
5505
+ };
5506
+ if (rowStruct) accessibleCell.call(this, cell, rowStruct, cellRenderer);else cellRenderer();
5507
+ }
5508
+ function renderCellText(cell) {
5509
+ const doc = this.document;
5510
+ const rollbackFont = doc._fontSource;
5511
+ const rollbackFontSize = doc._fontSize;
5512
+ const rollbackFontFamily = doc._fontFamily;
5513
+ if (cell.customFont) {
5514
+ if (cell.font.src) doc.font(cell.font.src, cell.font.family);
5515
+ if (cell.font.size) doc.fontSize(cell.font.size);
5516
+ }
5517
+ const x = cell.textX;
5518
+ const y = cell.textY;
5519
+ const Ah = cell.textAllocatedHeight;
5520
+ const Aw = cell.textAllocatedWidth;
5521
+ const Cw = cell.textBounds.width;
5522
+ const Ch = cell.textBounds.height;
5523
+ const Ox = -cell.textBounds.x;
5524
+ const Oy = -cell.textBounds.y;
5525
+ const PxScale = cell.align.x === 'right' ? 1 : cell.align.x === 'center' ? 0.5 : 0;
5526
+ const Px = (Aw - Cw) * PxScale;
5527
+ const PyScale = cell.align.y === 'bottom' ? 1 : cell.align.y === 'center' ? 0.5 : 0;
5528
+ const Py = (Ah - Ch) * PyScale;
5529
+ const dx = Px + Ox;
5530
+ const dy = Py + Oy;
5531
+ if (cell.debug) {
5532
+ doc.save();
5533
+ doc.dash(1, {
5534
+ space: 1
5535
+ }).lineWidth(1).strokeOpacity(0.3);
5536
+ if (cell.text) {
5537
+ doc.moveTo(x + Px, y).lineTo(x + Px, y + Ah).moveTo(x + Px + Cw, y).lineTo(x + Px + Cw, y + Ah).stroke('blue').moveTo(x, y + Py).lineTo(x + Aw, y + Py).moveTo(x, y + Py + Ch).lineTo(x + Aw, y + Py + Ch).stroke('green');
5538
+ }
5539
+ doc.rect(x, y, Aw, Ah).stroke('orange');
5540
+ doc.restore();
5541
+ }
5542
+ doc.save().rect(x, y, Aw, Ah).clip();
5543
+ doc.fillColor(cell.textColor).strokeColor(cell.textStrokeColor);
5544
+ if (cell.textStroke > 0) doc.lineWidth(cell.textStroke);
5545
+ doc.text(cell.text, x + dx, y + dy, cell.textOptions);
5546
+ doc.restore();
5547
+ if (cell.font) doc.font(rollbackFont, rollbackFontFamily, rollbackFontSize);
5548
+ }
5549
+ function renderBorder(border, borderColor, x, y, width, height, mask) {
5550
+ border = Object.fromEntries(Object.entries(border).map(([k, v]) => [k, mask && !mask[k] ? 0 : v]));
5551
+ const doc = this.document;
5552
+ if ([border.right, border.bottom, border.left].every(val => val === border.top)) {
5553
+ if (border.top > 0) {
5554
+ doc.save().lineWidth(border.top).rect(x, y, width, height).stroke(borderColor.top).restore();
5555
+ }
5556
+ } else {
5557
+ if (border.top > 0) {
5558
+ doc.save().lineWidth(border.top).moveTo(x, y).lineTo(x + width, y).stroke(borderColor.top).restore();
5559
+ }
5560
+ if (border.right > 0) {
5561
+ doc.save().lineWidth(border.right).moveTo(x + width, y).lineTo(x + width, y + height).stroke(borderColor.right).restore();
5562
+ }
5563
+ if (border.bottom > 0) {
5564
+ doc.save().lineWidth(border.bottom).moveTo(x + width, y + height).lineTo(x, y + height).stroke(borderColor.bottom).restore();
5565
+ }
5566
+ if (border.left > 0) {
5567
+ doc.save().lineWidth(border.left).moveTo(x, y + height).lineTo(x, y).stroke(borderColor.left).restore();
5568
+ }
5569
+ }
5570
+ }
5571
+
5572
+ class PDFTable {
5573
+ constructor(document, opts = {}) {
5574
+ this.document = document;
5575
+ this.opts = Object.freeze(opts);
5576
+ normalizeTable.call(this);
5577
+ accommodateTable.call(this);
5578
+ this._currRowIndex = 0;
5579
+ this._ended = false;
5580
+ if (opts.data) {
5581
+ for (const row of opts.data) this.row(row);
5582
+ return this.end();
5583
+ }
5584
+ }
5585
+ row(row, lastRow = false) {
5586
+ if (this._ended) {
5587
+ throw new Error(`Table was marked as ended on row ${this._currRowIndex}`);
5588
+ }
5589
+ row = Array.from(row);
5590
+ row = normalizeRow.call(this, row, this._currRowIndex);
5591
+ if (this._currRowIndex === 0) ensure.call(this, row);
5592
+ const {
5593
+ newPage,
5594
+ toRender
5595
+ } = measure.call(this, row, this._currRowIndex);
5596
+ if (newPage) this.document.continueOnNewPage();
5597
+ const yPos = renderRow.call(this, toRender, this._currRowIndex);
5598
+ this.document.x = this._position.x;
5599
+ this.document.y = yPos;
5600
+ if (lastRow) return this.end();
5601
+ this._currRowIndex++;
5602
+ return this;
5603
+ }
5604
+ end() {
5605
+ while (this._rowBuffer?.size) this.row([]);
5606
+ this._ended = true;
5607
+ accommodateCleanup.call(this);
5608
+ return this.document;
5609
+ }
5610
+ }
5611
+
5612
+ var TableMixin = {
5613
+ initTables() {
5614
+ this._tableIndex = 0;
5615
+ },
5616
+ table(opts) {
5617
+ return new PDFTable(this, opts);
5618
+ }
5619
+ };
5620
+
5304
5621
  class PDFMetadata {
5305
5622
  constructor() {
5306
5623
  this._metadata = `
@@ -5342,7 +5659,7 @@ var MetadataMixin = {
5342
5659
  _addInfo() {
5343
5660
  this.appendXML(`
5344
5661
  <rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/">
5345
- <xmp:CreateDate>${this.info.CreationDate.toISOString().split('.')[0] + "Z"}</xmp:CreateDate>
5662
+ <xmp:CreateDate>${this.info.CreationDate.toISOString().split('.')[0] + 'Z'}</xmp:CreateDate>
5346
5663
  <xmp:CreatorTool>${this.info.Creator}</xmp:CreatorTool>
5347
5664
  </rdf:Description>
5348
5665
  `);
@@ -5395,11 +5712,6 @@ var MetadataMixin = {
5395
5712
  endMetadata() {
5396
5713
  this._addInfo();
5397
5714
  this.metadata.end();
5398
-
5399
- /*
5400
- Metadata was introduced in PDF 1.4, so adding it to 1.3
5401
- will likely only take up more space.
5402
- */
5403
5715
  if (this.version != 1.3) {
5404
5716
  this.metadataRef = this.ref({
5405
5717
  length: this.metadata.getLength(),
@@ -5414,16 +5726,10 @@ var MetadataMixin = {
5414
5726
  }
5415
5727
  };
5416
5728
 
5417
- /*
5418
- PDFDocument - represents an entire PDF document
5419
- By Devon Govett
5420
- */
5421
5729
  class PDFDocument extends stream.Readable {
5422
5730
  constructor(options = {}) {
5423
5731
  super(options);
5424
5732
  this.options = options;
5425
-
5426
- // PDF version
5427
5733
  switch (options.pdfVersion) {
5428
5734
  case '1.4':
5429
5735
  this.version = 1.4;
@@ -5442,13 +5748,9 @@ class PDFDocument extends stream.Readable {
5442
5748
  this.version = 1.3;
5443
5749
  break;
5444
5750
  }
5445
-
5446
- // Whether streams should be compressed
5447
5751
  this.compress = this.options.compress != null ? this.options.compress : true;
5448
5752
  this._pageBuffer = [];
5449
5753
  this._pageBufferStart = 0;
5450
-
5451
- // The PDF object store
5452
5754
  this._offsets = [];
5453
5755
  this._waiting = 0;
5454
5756
  this._ended = false;
@@ -5469,11 +5771,7 @@ class PDFDocument extends stream.Readable {
5469
5771
  if (this.options.lang) {
5470
5772
  this._root.data.Lang = new String(this.options.lang);
5471
5773
  }
5472
-
5473
- // The current page
5474
5774
  this.page = null;
5475
-
5476
- // Initialize mixins
5477
5775
  this.initMetadata();
5478
5776
  this.initColor();
5479
5777
  this.initVector();
@@ -5482,9 +5780,8 @@ class PDFDocument extends stream.Readable {
5482
5780
  this.initImages();
5483
5781
  this.initOutline();
5484
5782
  this.initMarkings(options);
5783
+ this.initTables();
5485
5784
  this.initSubset(options);
5486
-
5487
- // Initialize the metadata
5488
5785
  this.info = {
5489
5786
  Producer: 'PDFKit',
5490
5787
  Creator: 'PDFKit',
@@ -5501,21 +5798,10 @@ class PDFDocument extends stream.Readable {
5501
5798
  DisplayDocTitle: true
5502
5799
  });
5503
5800
  }
5504
-
5505
- // Generate file ID
5506
5801
  this._id = PDFSecurity.generateFileID(this.info);
5507
-
5508
- // Initialize security settings
5509
5802
  this._security = PDFSecurity.create(this, options);
5510
-
5511
- // Write the header
5512
- // PDF version
5513
5803
  this._write(`%PDF-${this.version}`);
5514
-
5515
- // 4 binary chars, as recommended by the spec
5516
5804
  this._write('%\xFF\xFF\xFF\xFF');
5517
-
5518
- // Add the first page
5519
5805
  if (this.options.autoFirstPage !== false) {
5520
5806
  this.addPage();
5521
5807
  }
@@ -5526,27 +5812,16 @@ class PDFDocument extends stream.Readable {
5526
5812
  options
5527
5813
  } = this);
5528
5814
  }
5529
-
5530
- // end the current page if needed
5531
5815
  if (!this.options.bufferPages) {
5532
5816
  this.flushPages();
5533
5817
  }
5534
-
5535
- // create a page object
5536
5818
  this.page = new PDFPage(this, options);
5537
5819
  this._pageBuffer.push(this.page);
5538
-
5539
- // add the page to the object store
5540
5820
  const pages = this._root.data.Pages.data;
5541
5821
  pages.Kids.push(this.page.dictionary);
5542
5822
  pages.Count++;
5543
-
5544
- // reset x and y coordinates
5545
5823
  this.x = this.page.margins.left;
5546
5824
  this.y = this.page.margins.top;
5547
-
5548
- // flip PDF coordinate system so that the origin is in
5549
- // the top left rather than the bottom left
5550
5825
  this._ctm = [1, 0, 0, 1, 0, 0];
5551
5826
  this.transform(1, 0, 0, -1, 0, this.page.height);
5552
5827
  this.emit('pageAdded');
@@ -5554,7 +5829,7 @@ class PDFDocument extends stream.Readable {
5554
5829
  }
5555
5830
  continueOnNewPage(options) {
5556
5831
  const pageMarkings = this.endPageMarkings(this.page);
5557
- this.addPage(options);
5832
+ this.addPage(options ?? this.page._options);
5558
5833
  this.initPageMarkings(pageMarkings);
5559
5834
  return this;
5560
5835
  }
@@ -5572,8 +5847,6 @@ class PDFDocument extends stream.Readable {
5572
5847
  return this.page = page;
5573
5848
  }
5574
5849
  flushPages() {
5575
- // this local variable exists so we're future-proof against
5576
- // reentrant calls to flushPages.
5577
5850
  const pages = this._pageBuffer;
5578
5851
  this._pageBuffer = [];
5579
5852
  this._pageBufferStart += pages.length;
@@ -5594,13 +5867,10 @@ class PDFDocument extends stream.Readable {
5594
5867
  }
5595
5868
  addNamedEmbeddedFile(name, ref) {
5596
5869
  if (!this._root.data.Names.data.EmbeddedFiles) {
5597
- // disabling /Limits for this tree fixes attachments not showing in Adobe Reader
5598
5870
  this._root.data.Names.data.EmbeddedFiles = new PDFNameTree({
5599
5871
  limits: false
5600
5872
  });
5601
5873
  }
5602
-
5603
- // add filespec to EmbeddedFiles
5604
5874
  this._root.data.Names.data.EmbeddedFiles.add(name, ref);
5605
5875
  }
5606
5876
  addNamedJavaScript(name, js) {
@@ -5615,19 +5885,17 @@ class PDFDocument extends stream.Readable {
5615
5885
  }
5616
5886
  ref(data) {
5617
5887
  const ref = new PDFReference(this, this._offsets.length + 1, data);
5618
- this._offsets.push(null); // placeholder for this object's offset once it is finalized
5888
+ this._offsets.push(null);
5619
5889
  this._waiting++;
5620
5890
  return ref;
5621
5891
  }
5622
5892
  _read() {}
5623
- // do nothing, but this method is required by node
5624
-
5625
5893
  _write(data) {
5626
5894
  if (!Buffer.isBuffer(data)) {
5627
5895
  data = Buffer.from(data + '\n', 'binary');
5628
5896
  }
5629
5897
  this.push(data);
5630
- return this._offset += data.length;
5898
+ this._offset += data.length;
5631
5899
  }
5632
5900
  addContent(data) {
5633
5901
  this.page.write(data);
@@ -5637,7 +5905,7 @@ class PDFDocument extends stream.Readable {
5637
5905
  this._offsets[ref.id - 1] = ref.offset;
5638
5906
  if (--this._waiting === 0 && this._ended) {
5639
5907
  this._finalize();
5640
- return this._ended = false;
5908
+ this._ended = false;
5641
5909
  }
5642
5910
  }
5643
5911
  end() {
@@ -5674,13 +5942,12 @@ class PDFDocument extends stream.Readable {
5674
5942
  this._security.end();
5675
5943
  }
5676
5944
  if (this._waiting === 0) {
5677
- return this._finalize();
5945
+ this._finalize();
5678
5946
  } else {
5679
- return this._ended = true;
5947
+ this._ended = true;
5680
5948
  }
5681
5949
  }
5682
5950
  _finalize() {
5683
- // generate xref
5684
5951
  const xRefOffset = this._offset;
5685
5952
  this._write('xref');
5686
5953
  this._write(`0 ${this._offsets.length + 1}`);
@@ -5689,8 +5956,6 @@ class PDFDocument extends stream.Readable {
5689
5956
  offset = `0000000000${offset}`.slice(-10);
5690
5957
  this._write(offset + ' 00000 n ');
5691
5958
  }
5692
-
5693
- // trailer
5694
5959
  const trailer = {
5695
5960
  Size: this._offsets.length + 1,
5696
5961
  Root: this._root,
@@ -5705,9 +5970,7 @@ class PDFDocument extends stream.Readable {
5705
5970
  this._write('startxref');
5706
5971
  this._write(`${xRefOffset}`);
5707
5972
  this._write('%%EOF');
5708
-
5709
- // end the stream
5710
- return this.push(null);
5973
+ this.push(null);
5711
5974
  }
5712
5975
  toString() {
5713
5976
  return '[object PDFDocument]';
@@ -5728,6 +5991,7 @@ mixin(MarkingsMixin);
5728
5991
  mixin(AcroFormMixin);
5729
5992
  mixin(AttachmentsMixin);
5730
5993
  mixin(SubsetMixin);
5994
+ mixin(TableMixin);
5731
5995
  PDFDocument.LineWrapper = LineWrapper;
5732
5996
 
5733
5997
  module.exports = PDFDocument;