@kittl/pdfkit 0.17.3 → 0.17.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/js/pdfkit.js CHANGED
@@ -2,12 +2,14 @@
2
2
 
3
3
  var stream = require('stream');
4
4
  var zlib = require('zlib');
5
- var CryptoJS = require('crypto-js');
5
+ var utils = require('@noble/hashes/utils');
6
+ var md5 = require('js-md5');
7
+ var sha256 = require('@noble/hashes/sha256');
8
+ var aes = require('@noble/ciphers/aes');
6
9
  var fs = require('fs');
7
10
  var fontkit = require('fontkit');
8
11
  var events = require('events');
9
12
  var LineBreaker = require('linebreak');
10
- var exif = require('jpeg-exif');
11
13
  var PNG = require('png-js');
12
14
 
13
15
  class PDFAbstractReference {
@@ -359,6 +361,7 @@ class PDFPage {
359
361
  this._options = options;
360
362
  this.size = options.size || 'letter';
361
363
  this.layout = options.layout || 'portrait';
364
+ this.userUnit = options.userUnit || 1.0;
362
365
  const dimensions = Array.isArray(this.size) ? this.size : SIZES[this.size.toUpperCase()];
363
366
  this.width = dimensions[this.layout === 'portrait' ? 0 : 1];
364
367
  this.height = dimensions[this.layout === 'portrait' ? 1 : 0];
@@ -374,7 +377,8 @@ class PDFPage {
374
377
  Parent: this.document._root.data.Pages,
375
378
  MediaBox: [0, 0, this.width, this.height],
376
379
  Contents: this.content,
377
- Resources: this.resources
380
+ Resources: this.resources,
381
+ UserUnit: this.userUnit
378
382
  });
379
383
  this.markings = [];
380
384
  }
@@ -430,6 +434,7 @@ class PDFPage {
430
434
  for (let color of Object.values(this.document.spotColors)) {
431
435
  this.resources.data.ColorSpace[color.id] = color;
432
436
  }
437
+ this.document._writeSpaceToResources(this.resources);
433
438
  this.resources.end();
434
439
  return this.content.end();
435
440
  }
@@ -447,6 +452,58 @@ class PDFNameTree extends PDFTree {
447
452
  }
448
453
  }
449
454
 
455
+ function md5Hash(data) {
456
+ return new Uint8Array(md5.arrayBuffer(data));
457
+ }
458
+ function md5Hex(data) {
459
+ return md5(data);
460
+ }
461
+
462
+ function sha256Hash(data) {
463
+ return sha256.sha256(data);
464
+ }
465
+
466
+ function aesCbcEncrypt(data, key, iv, padding = true) {
467
+ return aes.cbc(key, iv, {
468
+ disablePadding: !padding
469
+ }).encrypt(data);
470
+ }
471
+ function aesEcbEncrypt(data, key) {
472
+ return aes.ecb(key, {
473
+ disablePadding: true
474
+ }).encrypt(data);
475
+ }
476
+
477
+ function rc4(data, key) {
478
+ const s = new Uint8Array(256);
479
+ for (let i = 0; i < 256; i++) {
480
+ s[i] = i;
481
+ }
482
+ let j = 0;
483
+ for (let i = 0; i < 256; i++) {
484
+ j = j + s[i] + key[i % key.length] & 0xff;
485
+ [s[i], s[j]] = [s[j], s[i]];
486
+ }
487
+ const output = new Uint8Array(data.length);
488
+ for (let i = 0, j = 0, k = 0; k < data.length; k++) {
489
+ i = i + 1 & 0xff;
490
+ j = j + s[i] & 0xff;
491
+ [s[i], s[j]] = [s[j], s[i]];
492
+ output[k] = data[k] ^ s[s[i] + s[j] & 0xff];
493
+ }
494
+ return output;
495
+ }
496
+
497
+ function randomBytes(length) {
498
+ const bytes = new Uint8Array(length);
499
+ if (globalThis.crypto?.getRandomValues) {
500
+ globalThis.crypto.getRandomValues(bytes);
501
+ } else {
502
+ require('crypto').randomFillSync(bytes);
503
+ }
504
+ return bytes;
505
+ }
506
+
450
507
  function inRange(value, rangeGroup) {
451
508
  if (value < rangeGroup[0]) return false;
452
509
  let startRange = 0;
@@ -545,10 +602,10 @@ class PDFSecurity {
545
602
  }
546
603
  infoStr += `${key}: ${info[key].valueOf()}\n`;
547
604
  }
548
- return wordArrayToBuffer(CryptoJS.MD5(infoStr));
605
+ return Buffer.from(md5Hash(infoStr));
549
606
  }
550
607
  static generateRandomWordArray(bytes) {
551
- return CryptoJS.lib.WordArray.random(bytes);
608
+ return randomBytes(bytes);
552
609
  }
553
610
  static create(document, options = {}) {
554
611
  if (!options.ownerPassword && !options.userPassword) {
@@ -640,8 +697,8 @@ class PDFSecurity {
640
697
  encDict.StrF = 'StdCF';
641
698
  }
642
699
  encDict.R = r;
643
- encDict.O = wordArrayToBuffer(ownerPasswordEntry);
644
- encDict.U = wordArrayToBuffer(userPasswordEntry);
700
+ encDict.O = Buffer.from(ownerPasswordEntry);
701
+ encDict.U = Buffer.from(userPasswordEntry);
645
702
  encDict.P = permissions;
646
703
  }
647
704
  _setupEncryptionV5(encDict, options) {
@@ -651,10 +708,10 @@ class PDFSecurity {
651
708
  const processedOwnerPassword = options.ownerPassword ? processPasswordR5(options.ownerPassword) : processedUserPassword;
652
709
  this.encryptionKey = getEncryptionKeyR5(PDFSecurity.generateRandomWordArray);
653
710
  const userPasswordEntry = getUserPasswordR5(processedUserPassword, PDFSecurity.generateRandomWordArray);
654
- const userKeySalt = CryptoJS.lib.WordArray.create(userPasswordEntry.words.slice(10, 12), 8);
711
+ const userKeySalt = userPasswordEntry.slice(40, 48);
655
712
  const userEncryptionKeyEntry = getUserEncryptionKeyR5(processedUserPassword, userKeySalt, this.encryptionKey);
656
713
  const ownerPasswordEntry = getOwnerPasswordR5(processedOwnerPassword, userPasswordEntry, PDFSecurity.generateRandomWordArray);
657
- const ownerKeySalt = CryptoJS.lib.WordArray.create(ownerPasswordEntry.words.slice(10, 12), 8);
714
+ const ownerKeySalt = ownerPasswordEntry.slice(40, 48);
658
715
  const ownerEncryptionKeyEntry = getOwnerEncryptionKeyR5(processedOwnerPassword, ownerKeySalt, userPasswordEntry, this.encryptionKey);
659
716
  const permsEntry = getEncryptedPermissionsR5(permissions, this.encryptionKey, PDFSecurity.generateRandomWordArray);
660
717
  encDict.V = 5;
@@ -669,36 +726,37 @@ class PDFSecurity {
669
726
  encDict.StmF = 'StdCF';
670
727
  encDict.StrF = 'StdCF';
671
728
  encDict.R = 5;
672
- encDict.O = wordArrayToBuffer(ownerPasswordEntry);
673
- encDict.OE = wordArrayToBuffer(ownerEncryptionKeyEntry);
674
- encDict.U = wordArrayToBuffer(userPasswordEntry);
675
- encDict.UE = wordArrayToBuffer(userEncryptionKeyEntry);
729
+ encDict.O = Buffer.from(ownerPasswordEntry);
730
+ encDict.OE = Buffer.from(ownerEncryptionKeyEntry);
731
+ encDict.U = Buffer.from(userPasswordEntry);
732
+ encDict.UE = Buffer.from(userEncryptionKeyEntry);
676
733
  encDict.P = permissions;
677
- encDict.Perms = wordArrayToBuffer(permsEntry);
734
+ encDict.Perms = Buffer.from(permsEntry);
678
735
  }
679
736
  getEncryptFn(obj, gen) {
680
737
  let digest;
681
738
  if (this.version < 5) {
682
- digest = this.encryptionKey.clone().concat(CryptoJS.lib.WordArray.create([(obj & 0xff) << 24 | (obj & 0xff00) << 8 | obj >> 8 & 0xff00 | gen & 0xff, (gen & 0xff00) << 16], 5));
739
+ const suffix = new Uint8Array([obj & 0xff, obj >> 8 & 0xff, obj >> 16 & 0xff, gen & 0xff, gen >> 8 & 0xff]);
740
+ digest = utils.concatBytes(this.encryptionKey, suffix);
683
741
  }
684
742
  if (this.version === 1 || this.version === 2) {
685
- let key = CryptoJS.MD5(digest);
686
- key.sigBytes = Math.min(16, this.keyBits / 8 + 5);
687
- return buffer => wordArrayToBuffer(CryptoJS.RC4.encrypt(CryptoJS.lib.WordArray.create(buffer), key).ciphertext);
743
+ let key = md5Hash(digest);
744
+ const keyLen = Math.min(16, this.keyBits / 8 + 5);
745
+ key = key.slice(0, keyLen);
746
+ return buffer => Buffer.from(rc4(new Uint8Array(buffer), key));
688
747
  }
689
748
  let key;
690
749
  if (this.version === 4) {
691
- key = CryptoJS.MD5(digest.concat(CryptoJS.lib.WordArray.create([0x73416c54], 4)));
750
+ const saltMarker = new Uint8Array([0x73, 0x41, 0x6c, 0x54]);
751
+ key = md5Hash(utils.concatBytes(digest, saltMarker));
692
752
  } else {
693
753
  key = this.encryptionKey;
694
754
  }
695
755
  const iv = PDFSecurity.generateRandomWordArray(16);
696
- const options = {
697
- mode: CryptoJS.mode.CBC,
698
- padding: CryptoJS.pad.Pkcs7,
699
- iv
756
+ return buffer => {
757
+ const encrypted = aesCbcEncrypt(new Uint8Array(buffer), key, iv, true);
758
+ return Buffer.from(utils.concatBytes(iv, encrypted));
700
759
  };
701
- return buffer => wordArrayToBuffer(iv.clone().concat(CryptoJS.AES.encrypt(CryptoJS.lib.WordArray.create(buffer), key, options).ciphertext));
702
760
  }
703
761
  end() {
704
762
  this.dictionary.end();
@@ -749,89 +807,97 @@ function getPermissionsR3(permissionObject = {}) {
749
807
  return permissions;
750
808
  }
751
809
  function getUserPasswordR2(encryptionKey) {
752
- return CryptoJS.RC4.encrypt(processPasswordR2R3R4(), encryptionKey).ciphertext;
810
+ return rc4(processPasswordR2R3R4(), encryptionKey);
753
811
  }
754
812
  function getUserPasswordR3R4(documentId, encryptionKey) {
755
- const key = encryptionKey.clone();
756
- let cipher = CryptoJS.MD5(processPasswordR2R3R4().concat(CryptoJS.lib.WordArray.create(documentId)));
813
+ const key = encryptionKey.slice();
814
+ let cipher = md5Hash(utils.concatBytes(processPasswordR2R3R4(), new Uint8Array(documentId)));
757
815
  for (let i = 0; i < 20; i++) {
758
- const xorRound = Math.ceil(key.sigBytes / 4);
759
- for (let j = 0; j < xorRound; j++) {
760
- key.words[j] = encryptionKey.words[j] ^ (i | i << 8 | i << 16 | i << 24);
816
+ const xorKey = new Uint8Array(key.length);
817
+ for (let j = 0; j < key.length; j++) {
818
+ xorKey[j] = encryptionKey[j] ^ i;
761
819
  }
762
- cipher = CryptoJS.RC4.encrypt(cipher, key).ciphertext;
820
+ cipher = rc4(cipher, xorKey);
763
821
  }
764
- return cipher.concat(CryptoJS.lib.WordArray.create(null, 16));
822
+ const result = new Uint8Array(32);
823
+ result.set(cipher);
824
+ return result;
765
825
  }
766
826
  function getOwnerPasswordR2R3R4(r, keyBits, paddedUserPassword, paddedOwnerPassword) {
767
827
  let digest = paddedOwnerPassword;
768
828
  let round = r >= 3 ? 51 : 1;
769
829
  for (let i = 0; i < round; i++) {
770
- digest = CryptoJS.MD5(digest);
830
+ digest = md5Hash(digest);
771
831
  }
772
- const key = digest.clone();
773
- key.sigBytes = keyBits / 8;
832
+ const keyLen = keyBits / 8;
833
+ let key = digest.slice(0, keyLen);
774
834
  let cipher = paddedUserPassword;
775
835
  round = r >= 3 ? 20 : 1;
776
836
  for (let i = 0; i < round; i++) {
777
- const xorRound = Math.ceil(key.sigBytes / 4);
778
- for (let j = 0; j < xorRound; j++) {
779
- key.words[j] = digest.words[j] ^ (i | i << 8 | i << 16 | i << 24);
837
+ const xorKey = new Uint8Array(keyLen);
838
+ for (let j = 0; j < keyLen; j++) {
839
+ xorKey[j] = key[j] ^ i;
780
840
  }
781
- cipher = CryptoJS.RC4.encrypt(cipher, key).ciphertext;
841
+ cipher = rc4(cipher, xorKey);
782
842
  }
783
843
  return cipher;
784
844
  }
785
845
  function getEncryptionKeyR2R3R4(r, keyBits, documentId, paddedUserPassword, ownerPasswordEntry, permissions) {
786
- let key = paddedUserPassword.clone().concat(ownerPasswordEntry).concat(CryptoJS.lib.WordArray.create([lsbFirstWord(permissions)], 4)).concat(CryptoJS.lib.WordArray.create(documentId));
846
+ const permBytes = new Uint8Array([permissions & 0xff, permissions >> 8 & 0xff, permissions >> 16 & 0xff, permissions >> 24 & 0xff]);
847
+ let key = utils.concatBytes(paddedUserPassword, ownerPasswordEntry, permBytes, new Uint8Array(documentId));
787
848
  const round = r >= 3 ? 51 : 1;
849
+ const keyLen = keyBits / 8;
788
850
  for (let i = 0; i < round; i++) {
789
- key = CryptoJS.MD5(key);
790
- key.sigBytes = keyBits / 8;
851
+ key = md5Hash(key);
852
+ key = key.slice(0, keyLen);
791
853
  }
792
854
  return key;
793
855
  }
794
856
  function getUserPasswordR5(processedUserPassword, generateRandomWordArray) {
795
857
  const validationSalt = generateRandomWordArray(8);
796
858
  const keySalt = generateRandomWordArray(8);
797
- return CryptoJS.SHA256(processedUserPassword.clone().concat(validationSalt)).concat(validationSalt).concat(keySalt);
859
+ const hash = sha256Hash(utils.concatBytes(processedUserPassword, validationSalt));
860
+ return utils.concatBytes(hash, validationSalt, keySalt);
798
861
  }
799
862
  function getUserEncryptionKeyR5(processedUserPassword, userKeySalt, encryptionKey) {
800
- const key = CryptoJS.SHA256(processedUserPassword.clone().concat(userKeySalt));
801
- const options = {
802
- mode: CryptoJS.mode.CBC,
803
- padding: CryptoJS.pad.NoPadding,
804
- iv: CryptoJS.lib.WordArray.create(null, 16)
805
- };
806
- return CryptoJS.AES.encrypt(encryptionKey, key, options).ciphertext;
863
+ const key = sha256Hash(utils.concatBytes(processedUserPassword, userKeySalt));
864
+ const iv = new Uint8Array(16);
865
+ return aesCbcEncrypt(encryptionKey, key, iv, false);
807
866
  }
808
867
  function getOwnerPasswordR5(processedOwnerPassword, userPasswordEntry, generateRandomWordArray) {
809
868
  const validationSalt = generateRandomWordArray(8);
810
869
  const keySalt = generateRandomWordArray(8);
811
- return CryptoJS.SHA256(processedOwnerPassword.clone().concat(validationSalt).concat(userPasswordEntry)).concat(validationSalt).concat(keySalt);
870
+ const hash = sha256Hash(utils.concatBytes(processedOwnerPassword, validationSalt, userPasswordEntry));
871
+ return utils.concatBytes(hash, validationSalt, keySalt);
812
872
  }
813
873
  function getOwnerEncryptionKeyR5(processedOwnerPassword, ownerKeySalt, userPasswordEntry, encryptionKey) {
814
- const key = CryptoJS.SHA256(processedOwnerPassword.clone().concat(ownerKeySalt).concat(userPasswordEntry));
815
- const options = {
816
- mode: CryptoJS.mode.CBC,
817
- padding: CryptoJS.pad.NoPadding,
818
- iv: CryptoJS.lib.WordArray.create(null, 16)
819
- };
820
- return CryptoJS.AES.encrypt(encryptionKey, key, options).ciphertext;
874
+ const key = sha256Hash(utils.concatBytes(processedOwnerPassword, ownerKeySalt, userPasswordEntry));
875
+ const iv = new Uint8Array(16);
876
+ return aesCbcEncrypt(encryptionKey, key, iv, false);
821
877
  }
822
878
  function getEncryptionKeyR5(generateRandomWordArray) {
823
879
  return generateRandomWordArray(32);
824
880
  }
825
881
  function getEncryptedPermissionsR5(permissions, encryptionKey, generateRandomWordArray) {
826
- const cipher = CryptoJS.lib.WordArray.create([lsbFirstWord(permissions), 0xffffffff, 0x54616462], 12).concat(generateRandomWordArray(4));
827
- const options = {
828
- mode: CryptoJS.mode.ECB,
829
- padding: CryptoJS.pad.NoPadding
830
- };
831
- return CryptoJS.AES.encrypt(cipher, encryptionKey, options).ciphertext;
882
+ const data = new Uint8Array(16);
883
+ data[0] = permissions & 0xff;
884
+ data[1] = permissions >> 8 & 0xff;
885
+ data[2] = permissions >> 16 & 0xff;
886
+ data[3] = permissions >> 24 & 0xff;
887
+ data[4] = 0xff;
888
+ data[5] = 0xff;
889
+ data[6] = 0xff;
890
+ data[7] = 0xff;
891
+ data[8] = 0x54;
892
+ data[9] = 0x61;
893
+ data[10] = 0x64;
894
+ data[11] = 0x62;
895
+ const randomPart = generateRandomWordArray(4);
896
+ data.set(randomPart, 12);
897
+ return aesEcbEncrypt(data, encryptionKey);
832
898
  }
833
899
  function processPasswordR2R3R4(password = '') {
834
- const out = Buffer.alloc(32);
900
+ const out = new Uint8Array(32);
835
901
  const length = password.length;
836
902
  let index = 0;
837
903
  while (index < length && index < 32) {
@@ -846,26 +912,16 @@ function processPasswordR2R3R4(password = '') {
846
912
  out[index] = PASSWORD_PADDING[index - length];
847
913
  index++;
848
914
  }
849
- return CryptoJS.lib.WordArray.create(out);
915
+ return out;
850
916
  }
851
917
  function processPasswordR5(password = '') {
852
918
  password = unescape(encodeURIComponent(saslprep(password)));
853
919
  const length = Math.min(127, password.length);
854
- const out = Buffer.alloc(length);
920
+ const out = new Uint8Array(length);
855
921
  for (let i = 0; i < length; i++) {
856
922
  out[i] = password.charCodeAt(i);
857
923
  }
858
- return CryptoJS.lib.WordArray.create(out);
859
- }
860
- function lsbFirstWord(data) {
861
- return (data & 0xff) << 24 | (data & 0xff00) << 8 | data >> 8 & 0xff00 | data >> 24 & 0xff;
862
- }
863
- function wordArrayToBuffer(wordArray) {
864
- const byteArray = [];
865
- for (let i = 0; i < wordArray.sigBytes; i++) {
866
- byteArray.push(wordArray.words[Math.floor(i / 4)] >> 8 * (3 - i % 4) & 0xff);
867
- }
868
- return Buffer.from(byteArray);
924
+ return out;
869
925
  }
870
926
  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];
871
927
 
@@ -1163,6 +1219,7 @@ var ColorMixin = {
1163
1219
  this._opacityCount = 0;
1164
1220
  this._patternCount = 0;
1165
1221
  this._gradCount = 0;
1222
+ this._activeColorProfile = null;
1166
1223
  },
1167
1224
  _normalizeColor(color) {
1168
1225
  if (typeof color === 'string') {
@@ -1182,7 +1239,7 @@ var ColorMixin = {
1182
1239
  if (color.length === 3) {
1183
1240
  color = color.map(part => part / 255);
1184
1241
  } else if (color.length === 4) {
1185
- color = color.map(part => part / 100);
1242
+ color = color.map(part => part / 255);
1186
1243
  }
1187
1244
  return color;
1188
1245
  }
@@ -1222,6 +1279,13 @@ var ColorMixin = {
1222
1279
  if (color instanceof SpotColor) {
1223
1280
  return color.id;
1224
1281
  }
1282
+ if (this._activeColorProfile) {
1283
+ const profile = this._activeColorProfile;
1284
+ if (profile.channels !== color.length) {
1285
+ throw Error("Profile channels don't match color channels");
1286
+ }
1287
+ return profile.label;
1288
+ }
1225
1289
  return color.length === 4 ? 'DeviceCMYK' : 'DeviceRGB';
1226
1290
  },
1227
1291
  fillColor(color, opacity) {
@@ -1251,6 +1315,18 @@ var ColorMixin = {
1251
1315
  this._doOpacity(null, opacity);
1252
1316
  return this;
1253
1317
  },
1318
+ beingColorProfile(label) {
1319
+ const profile = this._colorProfiles[label];
1320
+ if (!profile) {
1321
+ throw Error('Invalid color space label, the profile should be set first');
1322
+ }
1323
+ this._activeColorProfile = profile;
1324
+ return this;
1325
+ },
1326
+ endColorProfile() {
1327
+ this._activeColorProfile = null;
1328
+ return this;
1329
+ },
1254
1330
  _doOpacity(fillOpacity, strokeOpacity) {
1255
1331
  let dictionary, name;
1256
1332
  if (fillOpacity == null && strokeOpacity == null) {
@@ -1473,86 +1549,176 @@ const parameters = {
1473
1549
  Z: 0,
1474
1550
  z: 0
1475
1551
  };
1552
+ const isCommand = function (c) {
1553
+ return c in parameters;
1554
+ };
1555
+ const isWsp = function (c) {
1556
+ const codePoint = c.codePointAt(0);
1557
+ return codePoint === 0x20 || codePoint === 0x9 || codePoint === 0xd || codePoint === 0xa;
1558
+ };
1559
+ const isDigit = function (c) {
1560
+ const codePoint = c.codePointAt(0);
1561
+ if (codePoint == null) {
1562
+ return false;
1563
+ }
1564
+ return 48 <= codePoint && codePoint <= 57;
1565
+ };
1566
+ const readNumber = function (string, cursor) {
1567
+ let i = cursor;
1568
+ let value = '';
1569
+ let state = 'none';
1570
+ for (; i < string.length; i += 1) {
1571
+ const c = string[i];
1572
+ if (c === '+' || c === '-') {
1573
+ if (state === 'none') {
1574
+ state = 'sign';
1575
+ value += c;
1576
+ continue;
1577
+ }
1578
+ if (state === 'e') {
1579
+ state = 'exponent_sign';
1580
+ value += c;
1581
+ continue;
1582
+ }
1583
+ }
1584
+ if (isDigit(c)) {
1585
+ if (state === 'none' || state === 'sign' || state === 'whole') {
1586
+ state = 'whole';
1587
+ value += c;
1588
+ continue;
1589
+ }
1590
+ if (state === 'decimal_point' || state === 'decimal') {
1591
+ state = 'decimal';
1592
+ value += c;
1593
+ continue;
1594
+ }
1595
+ if (state === 'e' || state === 'exponent_sign' || state === 'exponent') {
1596
+ state = 'exponent';
1597
+ value += c;
1598
+ continue;
1599
+ }
1600
+ }
1601
+ if (c === '.') {
1602
+ if (state === 'none' || state === 'sign' || state === 'whole') {
1603
+ state = 'decimal_point';
1604
+ value += c;
1605
+ continue;
1606
+ }
1607
+ }
1608
+ if (c === 'E' || c === 'e') {
1609
+ if (state === 'whole' || state === 'decimal_point' || state === 'decimal') {
1610
+ state = 'e';
1611
+ value += c;
1612
+ continue;
1613
+ }
1614
+ }
1615
+ break;
1616
+ }
1617
+ const number = Number.parseFloat(value);
1618
+ if (Number.isNaN(number)) {
1619
+ return [cursor, null];
1620
+ }
1621
+ return [i - 1, number];
1622
+ };
1476
1623
  const parse = function (path) {
1477
- let cmd;
1478
- const ret = [];
1624
+ const pathData = [];
1625
+ let command = null;
1479
1626
  let args = [];
1480
- let curArg = '';
1481
- let foundDecimal = false;
1482
- let params = 0;
1483
- for (let c of path) {
1484
- if (parameters[c] != null) {
1485
- params = parameters[c];
1486
- if (cmd) {
1487
- if (curArg.length > 0) {
1488
- args[args.length] = +curArg;
1627
+ let argsCount = 0;
1628
+ let canHaveComma = false;
1629
+ let hadComma = false;
1630
+ for (let i = 0; i < path.length; i += 1) {
1631
+ const c = path.charAt(i);
1632
+ if (isWsp(c)) {
1633
+ continue;
1634
+ }
1635
+ if (canHaveComma && c === ',') {
1636
+ if (hadComma) {
1637
+ break;
1638
+ }
1639
+ hadComma = true;
1640
+ continue;
1641
+ }
1642
+ if (isCommand(c)) {
1643
+ if (hadComma) {
1644
+ return pathData;
1645
+ }
1646
+ if (command == null) {
1647
+ if (c !== 'M' && c !== 'm') {
1648
+ return pathData;
1649
+ }
1650
+ } else {
1651
+ if (args.length !== 0) {
1652
+ return pathData;
1489
1653
  }
1490
- ret[ret.length] = {
1491
- cmd,
1654
+ }
1655
+ command = c;
1656
+ args = [];
1657
+ argsCount = parameters[command];
1658
+ canHaveComma = false;
1659
+ if (argsCount === 0) {
1660
+ pathData.push({
1661
+ command,
1492
1662
  args
1493
- };
1494
- args = [];
1495
- curArg = '';
1496
- foundDecimal = false;
1663
+ });
1497
1664
  }
1498
- cmd = c;
1499
- } else if ([' ', ','].includes(c) || c === '-' && curArg.length > 0 && curArg[curArg.length - 1] !== 'e' || c === '.' && foundDecimal) {
1500
- if (curArg.length === 0) {
1501
- continue;
1665
+ continue;
1666
+ }
1667
+ if (command == null) {
1668
+ return pathData;
1669
+ }
1670
+ let newCursor = i;
1671
+ let number = null;
1672
+ if (command === 'A' || command === 'a') {
1673
+ const position = args.length;
1674
+ if (position === 0 || position === 1) {
1675
+ if (c !== '+' && c !== '-') {
1676
+ [newCursor, number] = readNumber(path, i);
1677
+ }
1502
1678
  }
1503
- if (args.length === params) {
1504
- ret[ret.length] = {
1505
- cmd,
1506
- args
1507
- };
1508
- args = [+curArg];
1509
- if (cmd === 'M') {
1510
- cmd = 'L';
1679
+ if (position === 2 || position === 5 || position === 6) {
1680
+ [newCursor, number] = readNumber(path, i);
1681
+ }
1682
+ if (position === 3 || position === 4) {
1683
+ if (c === '0') {
1684
+ number = 0;
1511
1685
  }
1512
- if (cmd === 'm') {
1513
- cmd = 'l';
1686
+ if (c === '1') {
1687
+ number = 1;
1514
1688
  }
1515
- } else {
1516
- args[args.length] = +curArg;
1517
1689
  }
1518
- foundDecimal = c === '.';
1519
- curArg = ['-', '.'].includes(c) ? c : '';
1520
1690
  } else {
1521
- curArg += c;
1522
- if (c === '.') {
1523
- foundDecimal = true;
1524
- }
1525
- }
1526
- }
1527
- if (curArg.length > 0) {
1528
- if (args.length === params) {
1529
- ret[ret.length] = {
1530
- cmd,
1691
+ [newCursor, number] = readNumber(path, i);
1692
+ }
1693
+ if (number == null) {
1694
+ return pathData;
1695
+ }
1696
+ args.push(number);
1697
+ canHaveComma = true;
1698
+ hadComma = false;
1699
+ i = newCursor;
1700
+ if (args.length === argsCount) {
1701
+ pathData.push({
1702
+ command,
1531
1703
  args
1532
- };
1533
- args = [+curArg];
1534
- if (cmd === 'M') {
1535
- cmd = 'L';
1704
+ });
1705
+ if (command === 'M') {
1706
+ command = 'L';
1536
1707
  }
1537
- if (cmd === 'm') {
1538
- cmd = 'l';
1708
+ if (command === 'm') {
1709
+ command = 'l';
1539
1710
  }
1540
- } else {
1541
- args[args.length] = +curArg;
1711
+ args = [];
1542
1712
  }
1543
1713
  }
1544
- ret[ret.length] = {
1545
- cmd,
1546
- args
1547
- };
1548
- return ret;
1714
+ return pathData;
1549
1715
  };
1550
1716
  const apply = function (commands, doc) {
1551
1717
  cx = cy = px = py = sx = sy = 0;
1552
1718
  for (let i = 0; i < commands.length; i++) {
1553
1719
  const c = commands[i];
1554
- if (typeof runners[c.cmd] === 'function') {
1555
- runners[c.cmd](doc, c.args);
1720
+ if (typeof runners[c.command] === 'function') {
1721
+ runners[c.command](doc, c.args);
1556
1722
  }
1557
1723
  }
1558
1724
  };
@@ -2584,7 +2750,7 @@ begincmap
2584
2750
  1 begincodespacerange
2585
2751
  <0000><ffff>
2586
2752
  endcodespacerange
2587
- 1 beginbfrange
2753
+ ${ranges.length} beginbfrange
2588
2754
  ${ranges.join('\n')}
2589
2755
  endbfrange
2590
2756
  endcmap
@@ -2763,6 +2929,7 @@ class LineWrapper extends events.EventEmitter {
2763
2929
  this.document = document;
2764
2930
  this.horizontalScaling = options.horizontalScaling || 100;
2765
2931
  this.indent = (options.indent || 0) * this.horizontalScaling / 100;
2932
+ this.indentAllLines = options.indentAllLines || false;
2766
2933
  this.characterSpacing = (options.characterSpacing || 0) * this.horizontalScaling / 100;
2767
2934
  this.wordSpacing = (options.wordSpacing === 0) * this.horizontalScaling / 100;
2768
2935
  this.columns = options.columns || 1;
@@ -3004,7 +3171,13 @@ class LineWrapper extends events.EventEmitter {
3004
3171
  this.column = 1;
3005
3172
  this.startY = this.document.page.margins.top;
3006
3173
  this.maxY = this.document.page.maxY();
3007
- this.document.x = this.startX;
3174
+ if (this.indentAllLines) {
3175
+ const indent = this.continuedX || this.indent;
3176
+ this.document.x += indent;
3177
+ this.lineWidth -= indent;
3178
+ } else {
3179
+ this.document.x = this.startX;
3180
+ }
3008
3181
  if (this.document._fillColor) {
3009
3182
  this.document.fillColor(...this.document._fillColor);
3010
3183
  }
@@ -3408,7 +3581,11 @@ var TextMixin = {
3408
3581
  }
3409
3582
  const renderedWidth = options.textWidth + wordSpacing * (options.wordCount - 1) + characterSpacing * (text.length - 1);
3410
3583
  if (options.link != null) {
3411
- this.link(x, y, renderedWidth, this.currentLineHeight(), options.link);
3584
+ const linkOptions = {};
3585
+ if (this._currentStructureElement && this._currentStructureElement.dictionary.data.S === 'Link') {
3586
+ linkOptions.structParent = this._currentStructureElement;
3587
+ }
3588
+ this.link(x, y, renderedWidth, this.currentLineHeight(), options.link, linkOptions);
3412
3589
  }
3413
3590
  if (options.goTo != null) {
3414
3591
  this.goTo(x, y, renderedWidth, this.currentLineHeight(), options.goTo);
@@ -3537,6 +3714,47 @@ var TextMixin = {
3537
3714
  }
3538
3715
  };
3539
3716
 
3717
+ const parseExifOrientation = data => {
3718
+ if (!data || data.length < 20) return null;
3719
+ let pos = 2;
3720
+ while (pos < data.length - 4) {
3721
+ while (pos < data.length && data[pos] !== 0xff) pos++;
3722
+ if (pos >= data.length - 4) return null;
3723
+ const marker = data.readUInt16BE(pos);
3724
+ pos += 2;
3725
+ if (marker === 0xffda) return null;
3726
+ if (marker >= 0xffd0 && marker <= 0xffd9 || marker === 0xff01) continue;
3727
+ if (pos + 2 > data.length) return null;
3728
+ const segmentLength = data.readUInt16BE(pos);
3729
+ if (marker === 0xffe1 && pos + 8 <= data.length) {
3730
+ const exifHeader = data.subarray(pos + 2, pos + 8).toString('binary');
3731
+ if (exifHeader === 'Exif\x00\x00') {
3732
+ const tiffStart = pos + 8;
3733
+ if (tiffStart + 8 > data.length) return null;
3734
+ const byteOrder = data.subarray(tiffStart, tiffStart + 2).toString('ascii');
3735
+ const isLittleEndian = byteOrder === 'II';
3736
+ if (!isLittleEndian && byteOrder !== 'MM') return null;
3737
+ const read16 = isLittleEndian ? o => data.readUInt16LE(o) : o => data.readUInt16BE(o);
3738
+ const read32 = isLittleEndian ? o => data.readUInt32LE(o) : o => data.readUInt32BE(o);
3739
+ if (read16(tiffStart + 2) !== 42) return null;
3740
+ const ifdPos = tiffStart + read32(tiffStart + 4);
3741
+ if (ifdPos + 2 > data.length) return null;
3742
+ const entryCount = read16(ifdPos);
3743
+ for (let i = 0; i < entryCount; i++) {
3744
+ const entryPos = ifdPos + 2 + i * 12;
3745
+ if (entryPos + 12 > data.length) return null;
3746
+ if (read16(entryPos) === 0x0112) {
3747
+ const value = read16(entryPos + 8);
3748
+ return value >= 1 && value <= 8 ? value : null;
3749
+ }
3750
+ }
3751
+ return null;
3752
+ }
3753
+ }
3754
+ pos += segmentLength;
3755
+ }
3756
+ return null;
3757
+ };
3540
3758
  const MARKERS = [0xffc0, 0xffc1, 0xffc2, 0xffc3, 0xffc5, 0xffc6, 0xffc7, 0xffc8, 0xffc9, 0xffca, 0xffcb, 0xffcc, 0xffcd, 0xffce, 0xffcf];
3541
3759
  const COLOR_SPACE_MAP = {
3542
3760
  1: 'DeviceGray',
@@ -3551,9 +3769,11 @@ class JPEG {
3551
3769
  if (this.data.readUInt16BE(0) !== 0xffd8) {
3552
3770
  throw 'SOI not found in JPEG';
3553
3771
  }
3554
- this.orientation = exif.fromBuffer(this.data).Orientation || 1;
3772
+ this.orientation = parseExifOrientation(this.data) || 1;
3555
3773
  let pos = 2;
3556
3774
  while (pos < this.data.length) {
3775
+ while (pos < this.data.length && this.data[pos] !== 0xff) pos++;
3776
+ if (pos >= this.data.length) break;
3557
3777
  marker = this.data.readUInt16BE(pos);
3558
3778
  pos += 2;
3559
3779
  if (MARKERS.includes(marker)) {
@@ -3705,12 +3925,16 @@ class PNGImage {
3705
3925
  }
3706
3926
  loadIndexedAlphaChannel() {
3707
3927
  const transparency = this.image.transparency.indexed;
3928
+ const isInterlaced = this.image.interlaceMethod === 1;
3708
3929
  return this.image.decodePixels(pixels => {
3709
3930
  const alphaChannel = Buffer.alloc(this.width * this.height);
3710
3931
  let i = 0;
3711
3932
  for (let j = 0, end = pixels.length; j < end; j++) {
3712
3933
  alphaChannel[i++] = transparency[pixels[j]];
3713
3934
  }
3935
+ if (isInterlaced) {
3936
+ this.imgData = zlib.deflateSync(Buffer.from(pixels));
3937
+ }
3714
3938
  this.alphaChannel = zlib.deflateSync(alphaChannel);
3715
3939
  return this.finalize();
3716
3940
  });
@@ -3732,7 +3956,7 @@ class PDFImage {
3732
3956
  data = Buffer.from(new Uint8Array(src));
3733
3957
  } else {
3734
3958
  const split = src.split(',');
3735
- if (split[0].startsWith('data:') && split[0].endsWith(';base64,')) {
3959
+ if (split[0].startsWith('data:') && split[0].endsWith(';base64')) {
3736
3960
  if (split.length === 1) {
3737
3961
  throw Error('Empty base64');
3738
3962
  }
@@ -3942,6 +4166,12 @@ var ImagesMixin = {
3942
4166
  }
3943
4167
  };
3944
4168
 
4169
+ class PDFAnnotationReference {
4170
+ constructor(annotationRef) {
4171
+ this.annotationRef = annotationRef;
4172
+ }
4173
+ }
4174
+
3945
4175
  var AnnotationsMixin = {
3946
4176
  annotate(x, y, w, h, options) {
3947
4177
  options.Type = 'Annot';
@@ -3959,12 +4189,18 @@ var AnnotationsMixin = {
3959
4189
  if (typeof options.Dest === 'string') {
3960
4190
  options.Dest = new String(options.Dest);
3961
4191
  }
4192
+ const structParent = options.structParent;
4193
+ delete options.structParent;
3962
4194
  for (let key in options) {
3963
4195
  const val = options[key];
3964
4196
  options[key[0].toUpperCase() + key.slice(1)] = val;
3965
4197
  }
3966
4198
  const ref = this.ref(options);
3967
4199
  this.page.annotations.push(ref);
4200
+ if (structParent && typeof structParent.add === 'function') {
4201
+ const annotRef = new PDFAnnotationReference(ref);
4202
+ structParent.add(annotRef);
4203
+ }
3968
4204
  ref.end();
3969
4205
  return this;
3970
4206
  },
@@ -4008,6 +4244,9 @@ var AnnotationsMixin = {
4008
4244
  });
4009
4245
  options.A.end();
4010
4246
  }
4247
+ if (options.structParent && !options.Contents) {
4248
+ options.Contents = new String('');
4249
+ }
4011
4250
  return this.annotate(x, y, w, h, options);
4012
4251
  },
4013
4252
  _markup(x, y, w, h, options = {}) {
@@ -4079,15 +4318,26 @@ var AnnotationsMixin = {
4079
4318
  }
4080
4319
  };
4081
4320
 
4321
+ const DEFAULT_OPTIONS = {
4322
+ top: 0,
4323
+ left: 0,
4324
+ zoom: 0,
4325
+ fit: true,
4326
+ pageNumber: null,
4327
+ expanded: false
4328
+ };
4082
4329
  class PDFOutline {
4083
- constructor(document, parent, title, dest, options = {
4084
- expanded: false
4085
- }) {
4330
+ constructor(document, parent, title, dest, options = DEFAULT_OPTIONS) {
4086
4331
  this.document = document;
4087
4332
  this.options = options;
4088
4333
  this.outlineData = {};
4089
4334
  if (dest !== null) {
4090
- this.outlineData['Dest'] = [dest.dictionary, 'Fit'];
4335
+ const destWidth = dest.data.MediaBox[2];
4336
+ const destHeight = dest.data.MediaBox[3];
4337
+ const top = destHeight - (options.top || 0);
4338
+ const left = destWidth - (options.left || 0);
4339
+ const zoom = options.zoom || 0;
4340
+ this.outlineData['Dest'] = options.fit ? [dest, 'Fit'] : [dest, 'XYZ', left, top, zoom];
4091
4341
  }
4092
4342
  if (parent !== null) {
4093
4343
  this.outlineData['Parent'] = parent;
@@ -4098,10 +4348,10 @@ class PDFOutline {
4098
4348
  this.dictionary = this.document.ref(this.outlineData);
4099
4349
  this.children = [];
4100
4350
  }
4101
- addItem(title, options = {
4102
- expanded: false
4103
- }) {
4104
- const result = new PDFOutline(this.document, this.dictionary, title, this.document.page, options);
4351
+ addItem(title, options = DEFAULT_OPTIONS) {
4352
+ const pages = this.document._root.data.Pages.data.Kids;
4353
+ const dest = options.pageNumber != null ? pages[options.pageNumber] : this.document.page.dictionary;
4354
+ const result = new PDFOutline(this.document, this.dictionary, title, dest, options);
4105
4355
  this.children.push(result);
4106
4356
  return result;
4107
4357
  }
@@ -4137,7 +4387,7 @@ var OutlineMixin = {
4137
4387
  this.outline.endOutline();
4138
4388
  if (this.outline.children.length > 0) {
4139
4389
  this._root.data.Outlines = this.outline.dictionary;
4140
- return this._root.data.PageMode = 'UseOutlines';
4390
+ return this._root.data.PageMode = this._root.data.PageMode || 'UseOutlines';
4141
4391
  }
4142
4392
  }
4143
4393
  };
@@ -4208,6 +4458,9 @@ class PDFStructureElement {
4208
4458
  if (child instanceof PDFStructureContent) {
4209
4459
  this._addContentToParentTree(child);
4210
4460
  }
4461
+ if (child instanceof PDFAnnotationReference) {
4462
+ this._addAnnotationToParentTree(child.annotationRef);
4463
+ }
4211
4464
  if (typeof child === 'function' && this._attached) {
4212
4465
  child = this._contentForClosure(child);
4213
4466
  }
@@ -4223,6 +4476,12 @@ class PDFStructureElement {
4223
4476
  pageStructParents[mcid] = this.dictionary;
4224
4477
  });
4225
4478
  }
4479
+ _addAnnotationToParentTree(annotRef) {
4480
+ const parentTreeKey = this.document.createStructParentTreeNextKey();
4481
+ annotRef.data.StructParent = parentTreeKey;
4482
+ const parentTree = this.document.getStructParentTree();
4483
+ parentTree.add(parentTreeKey, this.dictionary);
4484
+ }
4226
4485
  setParent(parentRef) {
4227
4486
  if (this.dictionary.data.P) {
4228
4487
  throw new Error(`Structure element added to more than one parent`);
@@ -4254,11 +4513,17 @@ class PDFStructureElement {
4254
4513
  this._flush();
4255
4514
  }
4256
4515
  _isValidChild(child) {
4257
- return child instanceof PDFStructureElement || child instanceof PDFStructureContent || typeof child === 'function';
4516
+ return child instanceof PDFStructureElement || child instanceof PDFStructureContent || child instanceof PDFAnnotationReference || typeof child === 'function';
4258
4517
  }
4259
4518
  _contentForClosure(closure) {
4260
4519
  const content = this.document.markStructureContent(this.dictionary.data.S);
4520
+ const prevStructElement = this.document._currentStructureElement;
4521
+ this.document._currentStructureElement = this;
4522
+ const wasEnded = this._ended;
4523
+ this._ended = false;
4261
4524
  closure();
4525
+ this._ended = wasEnded;
4526
+ this.document._currentStructureElement = prevStructElement;
4262
4527
  this.document.endMarkedContent();
4263
4528
  this._addContentToParentTree(content);
4264
4529
  return content;
@@ -4311,6 +4576,15 @@ class PDFStructureElement {
4311
4576
  }
4312
4577
  });
4313
4578
  }
4579
+ if (child instanceof PDFAnnotationReference) {
4580
+ const pageRef = this.document.page.dictionary;
4581
+ const objr = {
4582
+ Type: 'OBJR',
4583
+ Obj: child.annotationRef,
4584
+ Pg: pageRef
4585
+ };
4586
+ this.dictionary.data.K.push(objr);
4587
+ }
4314
4588
  }
4315
4589
  }
4316
4590
 
@@ -4404,6 +4678,13 @@ var MarkingsMixin = {
4404
4678
  endMarkedContent() {
4405
4679
  this.page.markings.pop();
4406
4680
  this.addContent('EMC');
4681
+ if (this._textOptions) {
4682
+ delete this._textOptions.link;
4683
+ delete this._textOptions.goTo;
4684
+ delete this._textOptions.destination;
4685
+ delete this._textOptions.underline;
4686
+ delete this._textOptions.strike;
4687
+ }
4407
4688
  return this;
4408
4689
  },
4409
4690
  struct(type, options = {}, children = null) {
@@ -4850,7 +5131,7 @@ var AttachmentsMixin = {
4850
5131
  if (options.type) {
4851
5132
  refBody.Subtype = options.type.replace('/', '#2F');
4852
5133
  }
4853
- const checksum = CryptoJS.MD5(CryptoJS.lib.WordArray.create(new Uint8Array(data)));
5134
+ const checksum = md5Hex(new Uint8Array(data));
4854
5135
  refBody.Params.CheckSum = new String(checksum);
4855
5136
  refBody.Params.Size = data.byteLength;
4856
5137
  let ref;
@@ -4910,6 +5191,9 @@ var PDFA = {
4910
5191
  this._addColorOutputIntent();
4911
5192
  },
4912
5193
  _addColorOutputIntent() {
5194
+ if (this._root.data.OutputIntents && this._root.data.OutputIntents.length !== 0) {
5195
+ return;
5196
+ }
4913
5197
  const iccProfile = fs.readFileSync(`${__dirname}/data/sRGB_IEC61966_2_1.icc`);
4914
5198
  const colorProfileRef = this.ref({
4915
5199
  Length: iccProfile.length,
@@ -5493,7 +5777,7 @@ function renderRow(row, rowIndex) {
5493
5777
  function renderCell(cell, rowStruct) {
5494
5778
  const cellRenderer = () => {
5495
5779
  if (cell.backgroundColor != null) {
5496
- this.document.save().rect(cell.x, cell.y, cell.width, cell.height).fill(cell.backgroundColor).restore();
5780
+ this.document.save().fillColor(cell.backgroundColor).rect(cell.x, cell.y, cell.width, cell.height).fill().restore();
5497
5781
  }
5498
5782
  renderBorder.call(this, cell.border, cell.borderColor, cell.x, cell.y, cell.width, cell.height);
5499
5783
  if (cell.debug) {
@@ -5554,20 +5838,20 @@ function renderBorder(border, borderColor, x, y, width, height, mask) {
5554
5838
  const doc = this.document;
5555
5839
  if ([border.right, border.bottom, border.left].every(val => val === border.top)) {
5556
5840
  if (border.top > 0) {
5557
- doc.save().lineWidth(border.top).rect(x, y, width, height).stroke(borderColor.top).restore();
5841
+ doc.save().lineWidth(border.top).strokeColor(borderColor.top).rect(x, y, width, height).stroke().restore();
5558
5842
  }
5559
5843
  } else {
5560
5844
  if (border.top > 0) {
5561
- doc.save().lineWidth(border.top).moveTo(x, y).lineTo(x + width, y).stroke(borderColor.top).restore();
5845
+ doc.save().lineWidth(border.top).moveTo(x, y).strokeColor(borderColor.top).lineTo(x + width, y).stroke().restore();
5562
5846
  }
5563
5847
  if (border.right > 0) {
5564
- doc.save().lineWidth(border.right).moveTo(x + width, y).lineTo(x + width, y + height).stroke(borderColor.right).restore();
5848
+ doc.save().lineWidth(border.right).moveTo(x + width, y).strokeColor(borderColor.right).lineTo(x + width, y + height).stroke().restore();
5565
5849
  }
5566
5850
  if (border.bottom > 0) {
5567
- doc.save().lineWidth(border.bottom).moveTo(x + width, y + height).lineTo(x, y + height).stroke(borderColor.bottom).restore();
5851
+ doc.save().lineWidth(border.bottom).moveTo(x + width, y + height).strokeColor(borderColor.bottom).lineTo(x, y + height).stroke().restore();
5568
5852
  }
5569
5853
  if (border.left > 0) {
5570
- doc.save().lineWidth(border.left).moveTo(x, y + height).lineTo(x, y).stroke(borderColor.left).restore();
5854
+ doc.save().lineWidth(border.left).moveTo(x, y + height).strokeColor(borderColor.left).lineTo(x, y).stroke().restore();
5571
5855
  }
5572
5856
  }
5573
5857
  }
@@ -5729,6 +6013,73 @@ var MetadataMixin = {
5729
6013
  }
5730
6014
  };
5731
6015
 
6016
+ class ICCProfile {
6017
+ constructor(label, data, channels, alternate) {
6018
+ this.label = label;
6019
+ this.data = data;
6020
+ this.channels = channels;
6021
+ this.alternate = alternate;
6022
+ this.ref = null;
6023
+ this.streamRef = null;
6024
+ }
6025
+ embed(document) {
6026
+ if (this.ref) {
6027
+ return;
6028
+ }
6029
+ this.document = document;
6030
+ this.streamRef = this.document.ref({
6031
+ Alternate: this.alternate,
6032
+ N: this.channels,
6033
+ Length: this.data.length
6034
+ });
6035
+ this.streamRef.write(this.data);
6036
+ this.streamRef.end();
6037
+ this.ref = this.document.ref([`ICCBased ${this.streamRef}`]);
6038
+ this.ref.end();
6039
+ this.data = null;
6040
+ }
6041
+ }
6042
+
6043
+ var ColorSpaceMixin = {
6044
+ initColorSpace() {
6045
+ this._colorProfiles = {};
6046
+ },
6047
+ iccProfile(label, data, channels, alternate) {
6048
+ const profile = new ICCProfile(label, data, channels, alternate);
6049
+ profile.embed(this);
6050
+ this._colorProfiles[label] = profile;
6051
+ return this;
6052
+ },
6053
+ _writeSpaceToResources(resources) {
6054
+ resources.data.ColorSpace = resources.data.ColorSpace || {};
6055
+ Object.entries(this._colorProfiles).forEach(([k, v]) => {
6056
+ resources.data.ColorSpace[k] = v.ref;
6057
+ });
6058
+ }
6059
+ };
6060
+
6061
+ var OutputIntent = {
6062
+ initOutputIntent() {
6063
+ this._root.data.OutputIntents = this._root.data.OutputIntents || [];
6064
+ },
6065
+ outputIntent(type, s, info, outputConditionIdentifier, destOutputProfileLabel) {
6066
+ const profile = this._colorProfiles[destOutputProfileLabel];
6067
+ if (profile) {
6068
+ throw Error('Invalid color profile label, the profile should be set first');
6069
+ }
6070
+ const intentRef = this.ref({
6071
+ Type: type,
6072
+ S: s,
6073
+ Info: info,
6074
+ OutputConditionIdentifier: outputConditionIdentifier,
6075
+ DestOutputProfile: profile.ref
6076
+ });
6077
+ intentRef.end();
6078
+ this._root.data.OutputIntents.push(intentRef);
6079
+ return this;
6080
+ }
6081
+ };
6082
+
5732
6083
  class PDFDocument extends stream.Readable {
5733
6084
  constructor(options = {}) {
5734
6085
  super(options);
@@ -5774,8 +6125,13 @@ class PDFDocument extends stream.Readable {
5774
6125
  if (this.options.lang) {
5775
6126
  this._root.data.Lang = new String(this.options.lang);
5776
6127
  }
6128
+ if (this.options.pageLayout) {
6129
+ const layout = this.options.pageLayout;
6130
+ this._root.data.PageLayout = layout.charAt(0).toUpperCase() + layout.slice(1);
6131
+ }
5777
6132
  this.page = null;
5778
6133
  this.initMetadata();
6134
+ this.initColorSpace();
5779
6135
  this.initColor();
5780
6136
  this.initVector();
5781
6137
  this.initFonts(options.font);
@@ -5785,6 +6141,7 @@ class PDFDocument extends stream.Readable {
5785
6141
  this.initMarkings(options);
5786
6142
  this.initTables();
5787
6143
  this.initSubset(options);
6144
+ this.initOutputIntent();
5788
6145
  this.info = {
5789
6146
  Producer: 'PDFKit',
5790
6147
  Creator: 'PDFKit',
@@ -5983,6 +6340,7 @@ const mixin = methods => {
5983
6340
  Object.assign(PDFDocument.prototype, methods);
5984
6341
  };
5985
6342
  mixin(MetadataMixin);
6343
+ mixin(ColorSpaceMixin);
5986
6344
  mixin(ColorMixin);
5987
6345
  mixin(VectorMixin);
5988
6346
  mixin(FontsMixin);
@@ -5995,6 +6353,7 @@ mixin(AcroFormMixin);
5995
6353
  mixin(AttachmentsMixin);
5996
6354
  mixin(SubsetMixin);
5997
6355
  mixin(TableMixin);
6356
+ mixin(OutputIntent);
5998
6357
  PDFDocument.LineWrapper = LineWrapper;
5999
6358
 
6000
6359
  module.exports = PDFDocument;