pdfkit 0.17.2 → 0.19.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
@@ -2,12 +2,13 @@
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 sha2 = require('@noble/hashes/sha2');
8
+ var aes = require('@noble/ciphers/aes');
6
9
  var fs = require('fs');
7
10
  var fontkit = require('fontkit');
8
- var events = require('events');
9
11
  var LineBreaker = require('linebreak');
10
- var exif = require('jpeg-exif');
11
12
  var PNG = require('png-js');
12
13
 
13
14
  class PDFAbstractReference {
@@ -59,7 +60,7 @@ class SpotColor {
59
60
  this.id = 'CS' + Object.keys(doc.spotColors).length;
60
61
  this.name = name;
61
62
  this.values = [C, M, Y, K];
62
- this.ref = doc.ref(['Separation', this.name, 'DeviceCMYK', {
63
+ this.ref = doc.ref(['Separation', escapeName(this.name), 'DeviceCMYK', {
63
64
  Range: [0, 1, 0, 1, 0, 1, 0, 1],
64
65
  C0: [0, 0, 0, 0],
65
66
  C1: this.values.map(value => value / 100),
@@ -75,6 +76,10 @@ class SpotColor {
75
76
  }
76
77
 
77
78
  const pad = (str, length) => (Array(length + 1).join('0') + str).slice(-length);
79
+ const isSafeCharCode = code => {
80
+ if (code > 0x7f) return true;
81
+ return code > 0x20 && code !== 0x7f && code !== 0x23 && code !== 0x25 && code !== 0x28 && code !== 0x29 && code !== 0x2f && code !== 0x3c && code !== 0x3e && code !== 0x5b && code !== 0x5d && code !== 0x7b && code !== 0x7d;
82
+ };
78
83
  const escapableRe = /[\n\r\t\b\f()\\]/g;
79
84
  const escapable = {
80
85
  '\n': '\\n',
@@ -86,6 +91,18 @@ const escapable = {
86
91
  '(': '\\(',
87
92
  ')': '\\)'
88
93
  };
94
+ const escapeName = function (name) {
95
+ let escapedName = '';
96
+ for (const char of name) {
97
+ const code = char.charCodeAt(0);
98
+ if (isSafeCharCode(code)) {
99
+ escapedName += char;
100
+ } else {
101
+ escapedName += `#${code.toString(16).toUpperCase().padStart(2, '0')}`;
102
+ }
103
+ }
104
+ return escapedName;
105
+ };
89
106
  const swapBytes = function (buff) {
90
107
  const l = buff.length;
91
108
  if (l & 0x01) {
@@ -359,6 +376,7 @@ class PDFPage {
359
376
  this._options = options;
360
377
  this.size = options.size || 'letter';
361
378
  this.layout = options.layout || 'portrait';
379
+ this.userUnit = options.userUnit || 1.0;
362
380
  const dimensions = Array.isArray(this.size) ? this.size : SIZES[this.size.toUpperCase()];
363
381
  this.width = dimensions[this.layout === 'portrait' ? 0 : 1];
364
382
  this.height = dimensions[this.layout === 'portrait' ? 1 : 0];
@@ -374,7 +392,8 @@ class PDFPage {
374
392
  Parent: this.document._root.data.Pages,
375
393
  MediaBox: [0, 0, this.width, this.height],
376
394
  Contents: this.content,
377
- Resources: this.resources
395
+ Resources: this.resources,
396
+ UserUnit: this.userUnit
378
397
  });
379
398
  this.markings = [];
380
399
  }
@@ -447,6 +466,54 @@ class PDFNameTree extends PDFTree {
447
466
  }
448
467
  }
449
468
 
469
+ function md5Hash(data) {
470
+ return new Uint8Array(md5.arrayBuffer(data));
471
+ }
472
+ function md5Hex(data) {
473
+ return md5(data);
474
+ }
475
+
476
+ function sha256Hash(data) {
477
+ return sha2.sha256(data);
478
+ }
479
+
480
+ function aesCbcEncrypt(data, key, iv, padding = true) {
481
+ return aes.cbc(key, iv, {
482
+ disablePadding: !padding
483
+ }).encrypt(data);
484
+ }
485
+ function aesEcbEncrypt(data, key) {
486
+ return aes.ecb(key, {
487
+ disablePadding: true
488
+ }).encrypt(data);
489
+ }
490
+
491
+ function rc4(data, key) {
492
+ const s = new Uint8Array(256);
493
+ for (let i = 0; i < 256; i++) {
494
+ s[i] = i;
495
+ }
496
+ let j = 0;
497
+ for (let i = 0; i < 256; i++) {
498
+ j = j + s[i] + key[i % key.length] & 0xff;
499
+ [s[i], s[j]] = [s[j], s[i]];
500
+ }
501
+ const output = new Uint8Array(data.length);
502
+ for (let i = 0, j = 0, k = 0; k < data.length; k++) {
503
+ i = i + 1 & 0xff;
504
+ j = j + s[i] & 0xff;
505
+ [s[i], s[j]] = [s[j], s[i]];
506
+ output[k] = data[k] ^ s[s[i] + s[j] & 0xff];
507
+ }
508
+ return output;
509
+ }
510
+
511
+ function randomBytes(length) {
512
+ const bytes = new Uint8Array(length);
513
+ globalThis.crypto.getRandomValues(bytes);
514
+ return bytes;
515
+ }
516
+
450
517
  function inRange(value, rangeGroup) {
451
518
  if (value < rangeGroup[0]) return false;
452
519
  let startRange = 0;
@@ -545,10 +612,10 @@ class PDFSecurity {
545
612
  }
546
613
  infoStr += `${key}: ${info[key].valueOf()}\n`;
547
614
  }
548
- return wordArrayToBuffer(CryptoJS.MD5(infoStr));
615
+ return Buffer.from(md5Hash(infoStr));
549
616
  }
550
617
  static generateRandomWordArray(bytes) {
551
- return CryptoJS.lib.WordArray.random(bytes);
618
+ return randomBytes(bytes);
552
619
  }
553
620
  static create(document, options = {}) {
554
621
  if (!options.ownerPassword && !options.userPassword) {
@@ -640,8 +707,8 @@ class PDFSecurity {
640
707
  encDict.StrF = 'StdCF';
641
708
  }
642
709
  encDict.R = r;
643
- encDict.O = wordArrayToBuffer(ownerPasswordEntry);
644
- encDict.U = wordArrayToBuffer(userPasswordEntry);
710
+ encDict.O = Buffer.from(ownerPasswordEntry);
711
+ encDict.U = Buffer.from(userPasswordEntry);
645
712
  encDict.P = permissions;
646
713
  }
647
714
  _setupEncryptionV5(encDict, options) {
@@ -651,10 +718,10 @@ class PDFSecurity {
651
718
  const processedOwnerPassword = options.ownerPassword ? processPasswordR5(options.ownerPassword) : processedUserPassword;
652
719
  this.encryptionKey = getEncryptionKeyR5(PDFSecurity.generateRandomWordArray);
653
720
  const userPasswordEntry = getUserPasswordR5(processedUserPassword, PDFSecurity.generateRandomWordArray);
654
- const userKeySalt = CryptoJS.lib.WordArray.create(userPasswordEntry.words.slice(10, 12), 8);
721
+ const userKeySalt = userPasswordEntry.slice(40, 48);
655
722
  const userEncryptionKeyEntry = getUserEncryptionKeyR5(processedUserPassword, userKeySalt, this.encryptionKey);
656
723
  const ownerPasswordEntry = getOwnerPasswordR5(processedOwnerPassword, userPasswordEntry, PDFSecurity.generateRandomWordArray);
657
- const ownerKeySalt = CryptoJS.lib.WordArray.create(ownerPasswordEntry.words.slice(10, 12), 8);
724
+ const ownerKeySalt = ownerPasswordEntry.slice(40, 48);
658
725
  const ownerEncryptionKeyEntry = getOwnerEncryptionKeyR5(processedOwnerPassword, ownerKeySalt, userPasswordEntry, this.encryptionKey);
659
726
  const permsEntry = getEncryptedPermissionsR5(permissions, this.encryptionKey, PDFSecurity.generateRandomWordArray);
660
727
  encDict.V = 5;
@@ -669,36 +736,37 @@ class PDFSecurity {
669
736
  encDict.StmF = 'StdCF';
670
737
  encDict.StrF = 'StdCF';
671
738
  encDict.R = 5;
672
- encDict.O = wordArrayToBuffer(ownerPasswordEntry);
673
- encDict.OE = wordArrayToBuffer(ownerEncryptionKeyEntry);
674
- encDict.U = wordArrayToBuffer(userPasswordEntry);
675
- encDict.UE = wordArrayToBuffer(userEncryptionKeyEntry);
739
+ encDict.O = Buffer.from(ownerPasswordEntry);
740
+ encDict.OE = Buffer.from(ownerEncryptionKeyEntry);
741
+ encDict.U = Buffer.from(userPasswordEntry);
742
+ encDict.UE = Buffer.from(userEncryptionKeyEntry);
676
743
  encDict.P = permissions;
677
- encDict.Perms = wordArrayToBuffer(permsEntry);
744
+ encDict.Perms = Buffer.from(permsEntry);
678
745
  }
679
746
  getEncryptFn(obj, gen) {
680
747
  let digest;
681
748
  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));
749
+ const suffix = new Uint8Array([obj & 0xff, obj >> 8 & 0xff, obj >> 16 & 0xff, gen & 0xff, gen >> 8 & 0xff]);
750
+ digest = utils.concatBytes(this.encryptionKey, suffix);
683
751
  }
684
752
  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);
753
+ let key = md5Hash(digest);
754
+ const keyLen = Math.min(16, this.keyBits / 8 + 5);
755
+ key = key.slice(0, keyLen);
756
+ return buffer => Buffer.from(rc4(new Uint8Array(buffer), key));
688
757
  }
689
758
  let key;
690
759
  if (this.version === 4) {
691
- key = CryptoJS.MD5(digest.concat(CryptoJS.lib.WordArray.create([0x73416c54], 4)));
760
+ const saltMarker = new Uint8Array([0x73, 0x41, 0x6c, 0x54]);
761
+ key = md5Hash(utils.concatBytes(digest, saltMarker));
692
762
  } else {
693
763
  key = this.encryptionKey;
694
764
  }
695
765
  const iv = PDFSecurity.generateRandomWordArray(16);
696
- const options = {
697
- mode: CryptoJS.mode.CBC,
698
- padding: CryptoJS.pad.Pkcs7,
699
- iv
766
+ return buffer => {
767
+ const encrypted = aesCbcEncrypt(new Uint8Array(buffer), key, iv, true);
768
+ return Buffer.from(utils.concatBytes(iv, encrypted));
700
769
  };
701
- return buffer => wordArrayToBuffer(iv.clone().concat(CryptoJS.AES.encrypt(CryptoJS.lib.WordArray.create(buffer), key, options).ciphertext));
702
770
  }
703
771
  end() {
704
772
  this.dictionary.end();
@@ -749,89 +817,97 @@ function getPermissionsR3(permissionObject = {}) {
749
817
  return permissions;
750
818
  }
751
819
  function getUserPasswordR2(encryptionKey) {
752
- return CryptoJS.RC4.encrypt(processPasswordR2R3R4(), encryptionKey).ciphertext;
820
+ return rc4(processPasswordR2R3R4(), encryptionKey);
753
821
  }
754
822
  function getUserPasswordR3R4(documentId, encryptionKey) {
755
- const key = encryptionKey.clone();
756
- let cipher = CryptoJS.MD5(processPasswordR2R3R4().concat(CryptoJS.lib.WordArray.create(documentId)));
823
+ const key = encryptionKey.slice();
824
+ let cipher = md5Hash(utils.concatBytes(processPasswordR2R3R4(), new Uint8Array(documentId)));
757
825
  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);
826
+ const xorKey = new Uint8Array(key.length);
827
+ for (let j = 0; j < key.length; j++) {
828
+ xorKey[j] = encryptionKey[j] ^ i;
761
829
  }
762
- cipher = CryptoJS.RC4.encrypt(cipher, key).ciphertext;
830
+ cipher = rc4(cipher, xorKey);
763
831
  }
764
- return cipher.concat(CryptoJS.lib.WordArray.create(null, 16));
832
+ const result = new Uint8Array(32);
833
+ result.set(cipher);
834
+ return result;
765
835
  }
766
836
  function getOwnerPasswordR2R3R4(r, keyBits, paddedUserPassword, paddedOwnerPassword) {
767
837
  let digest = paddedOwnerPassword;
768
838
  let round = r >= 3 ? 51 : 1;
769
839
  for (let i = 0; i < round; i++) {
770
- digest = CryptoJS.MD5(digest);
840
+ digest = md5Hash(digest);
771
841
  }
772
- const key = digest.clone();
773
- key.sigBytes = keyBits / 8;
842
+ const keyLen = keyBits / 8;
843
+ let key = digest.slice(0, keyLen);
774
844
  let cipher = paddedUserPassword;
775
845
  round = r >= 3 ? 20 : 1;
776
846
  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);
847
+ const xorKey = new Uint8Array(keyLen);
848
+ for (let j = 0; j < keyLen; j++) {
849
+ xorKey[j] = key[j] ^ i;
780
850
  }
781
- cipher = CryptoJS.RC4.encrypt(cipher, key).ciphertext;
851
+ cipher = rc4(cipher, xorKey);
782
852
  }
783
853
  return cipher;
784
854
  }
785
855
  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));
856
+ const permBytes = new Uint8Array([permissions & 0xff, permissions >> 8 & 0xff, permissions >> 16 & 0xff, permissions >> 24 & 0xff]);
857
+ let key = utils.concatBytes(paddedUserPassword, ownerPasswordEntry, permBytes, new Uint8Array(documentId));
787
858
  const round = r >= 3 ? 51 : 1;
859
+ const keyLen = keyBits / 8;
788
860
  for (let i = 0; i < round; i++) {
789
- key = CryptoJS.MD5(key);
790
- key.sigBytes = keyBits / 8;
861
+ key = md5Hash(key);
862
+ key = key.slice(0, keyLen);
791
863
  }
792
864
  return key;
793
865
  }
794
866
  function getUserPasswordR5(processedUserPassword, generateRandomWordArray) {
795
867
  const validationSalt = generateRandomWordArray(8);
796
868
  const keySalt = generateRandomWordArray(8);
797
- return CryptoJS.SHA256(processedUserPassword.clone().concat(validationSalt)).concat(validationSalt).concat(keySalt);
869
+ const hash = sha256Hash(utils.concatBytes(processedUserPassword, validationSalt));
870
+ return utils.concatBytes(hash, validationSalt, keySalt);
798
871
  }
799
872
  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;
873
+ const key = sha256Hash(utils.concatBytes(processedUserPassword, userKeySalt));
874
+ const iv = new Uint8Array(16);
875
+ return aesCbcEncrypt(encryptionKey, key, iv, false);
807
876
  }
808
877
  function getOwnerPasswordR5(processedOwnerPassword, userPasswordEntry, generateRandomWordArray) {
809
878
  const validationSalt = generateRandomWordArray(8);
810
879
  const keySalt = generateRandomWordArray(8);
811
- return CryptoJS.SHA256(processedOwnerPassword.clone().concat(validationSalt).concat(userPasswordEntry)).concat(validationSalt).concat(keySalt);
880
+ const hash = sha256Hash(utils.concatBytes(processedOwnerPassword, validationSalt, userPasswordEntry));
881
+ return utils.concatBytes(hash, validationSalt, keySalt);
812
882
  }
813
883
  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;
884
+ const key = sha256Hash(utils.concatBytes(processedOwnerPassword, ownerKeySalt, userPasswordEntry));
885
+ const iv = new Uint8Array(16);
886
+ return aesCbcEncrypt(encryptionKey, key, iv, false);
821
887
  }
822
888
  function getEncryptionKeyR5(generateRandomWordArray) {
823
889
  return generateRandomWordArray(32);
824
890
  }
825
891
  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;
892
+ const data = new Uint8Array(16);
893
+ data[0] = permissions & 0xff;
894
+ data[1] = permissions >> 8 & 0xff;
895
+ data[2] = permissions >> 16 & 0xff;
896
+ data[3] = permissions >> 24 & 0xff;
897
+ data[4] = 0xff;
898
+ data[5] = 0xff;
899
+ data[6] = 0xff;
900
+ data[7] = 0xff;
901
+ data[8] = 0x54;
902
+ data[9] = 0x61;
903
+ data[10] = 0x64;
904
+ data[11] = 0x62;
905
+ const randomPart = generateRandomWordArray(4);
906
+ data.set(randomPart, 12);
907
+ return aesEcbEncrypt(data, encryptionKey);
832
908
  }
833
909
  function processPasswordR2R3R4(password = '') {
834
- const out = Buffer.alloc(32);
910
+ const out = new Uint8Array(32);
835
911
  const length = password.length;
836
912
  let index = 0;
837
913
  while (index < length && index < 32) {
@@ -846,33 +922,23 @@ function processPasswordR2R3R4(password = '') {
846
922
  out[index] = PASSWORD_PADDING[index - length];
847
923
  index++;
848
924
  }
849
- return CryptoJS.lib.WordArray.create(out);
925
+ return out;
850
926
  }
851
927
  function processPasswordR5(password = '') {
852
928
  password = unescape(encodeURIComponent(saslprep(password)));
853
929
  const length = Math.min(127, password.length);
854
- const out = Buffer.alloc(length);
930
+ const out = new Uint8Array(length);
855
931
  for (let i = 0; i < length; i++) {
856
932
  out[i] = password.charCodeAt(i);
857
933
  }
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);
934
+ return out;
869
935
  }
870
936
  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
937
 
872
938
  const {
873
939
  number: number$2
874
940
  } = PDFObject;
875
- class PDFGradient$1 {
941
+ let PDFGradient$1 = class PDFGradient {
876
942
  constructor(doc) {
877
943
  this.doc = doc;
878
944
  this.stops = [];
@@ -1030,8 +1096,8 @@ class PDFGradient$1 {
1030
1096
  const op = stroke ? 'SCN' : 'scn';
1031
1097
  return this.doc.addContent(`/${this.id} ${op}`);
1032
1098
  }
1033
- }
1034
- class PDFLinearGradient$1 extends PDFGradient$1 {
1099
+ };
1100
+ let PDFLinearGradient$1 = class PDFLinearGradient extends PDFGradient$1 {
1035
1101
  constructor(doc, x1, y1, x2, y2) {
1036
1102
  super(doc);
1037
1103
  this.x1 = x1;
@@ -1049,10 +1115,10 @@ class PDFLinearGradient$1 extends PDFGradient$1 {
1049
1115
  });
1050
1116
  }
1051
1117
  opacityGradient() {
1052
- return new PDFLinearGradient$1(this.doc, this.x1, this.y1, this.x2, this.y2);
1118
+ return new PDFLinearGradient(this.doc, this.x1, this.y1, this.x2, this.y2);
1053
1119
  }
1054
- }
1055
- class PDFRadialGradient$1 extends PDFGradient$1 {
1120
+ };
1121
+ let PDFRadialGradient$1 = class PDFRadialGradient extends PDFGradient$1 {
1056
1122
  constructor(doc, x1, y1, r1, x2, y2, r2) {
1057
1123
  super(doc);
1058
1124
  this.doc = doc;
@@ -1073,9 +1139,9 @@ class PDFRadialGradient$1 extends PDFGradient$1 {
1073
1139
  });
1074
1140
  }
1075
1141
  opacityGradient() {
1076
- return new PDFRadialGradient$1(this.doc, this.x1, this.y1, this.r1, this.x2, this.y2, this.r2);
1142
+ return new PDFRadialGradient(this.doc, this.x1, this.y1, this.r1, this.x2, this.y2, this.r2);
1077
1143
  }
1078
- }
1144
+ };
1079
1145
  var Gradient = {
1080
1146
  PDFGradient: PDFGradient$1,
1081
1147
  PDFLinearGradient: PDFLinearGradient$1,
@@ -1083,7 +1149,7 @@ var Gradient = {
1083
1149
  };
1084
1150
 
1085
1151
  const underlyingColorSpaces = ['DeviceCMYK', 'DeviceRGB'];
1086
- class PDFTilingPattern$1 {
1152
+ let PDFTilingPattern$1 = class PDFTilingPattern {
1087
1153
  constructor(doc, bBox, xStep, yStep, stream) {
1088
1154
  this.doc = doc;
1089
1155
  this.bBox = bBox;
@@ -1143,7 +1209,7 @@ class PDFTilingPattern$1 {
1143
1209
  const op = stroke ? 'SCN' : 'scn';
1144
1210
  return this.doc.addContent(`${normalizedColor.join(' ')} /${this.id} ${op}`);
1145
1211
  }
1146
- }
1212
+ };
1147
1213
  var pattern = {
1148
1214
  PDFTilingPattern: PDFTilingPattern$1
1149
1215
  };
@@ -1473,86 +1539,176 @@ const parameters = {
1473
1539
  Z: 0,
1474
1540
  z: 0
1475
1541
  };
1542
+ const isCommand = function (c) {
1543
+ return c in parameters;
1544
+ };
1545
+ const isWsp = function (c) {
1546
+ const codePoint = c.codePointAt(0);
1547
+ return codePoint === 0x20 || codePoint === 0x9 || codePoint === 0xd || codePoint === 0xa;
1548
+ };
1549
+ const isDigit = function (c) {
1550
+ const codePoint = c.codePointAt(0);
1551
+ if (codePoint == null) {
1552
+ return false;
1553
+ }
1554
+ return 48 <= codePoint && codePoint <= 57;
1555
+ };
1556
+ const readNumber = function (string, cursor) {
1557
+ let i = cursor;
1558
+ let value = '';
1559
+ let state = 'none';
1560
+ for (; i < string.length; i += 1) {
1561
+ const c = string[i];
1562
+ if (c === '+' || c === '-') {
1563
+ if (state === 'none') {
1564
+ state = 'sign';
1565
+ value += c;
1566
+ continue;
1567
+ }
1568
+ if (state === 'e') {
1569
+ state = 'exponent_sign';
1570
+ value += c;
1571
+ continue;
1572
+ }
1573
+ }
1574
+ if (isDigit(c)) {
1575
+ if (state === 'none' || state === 'sign' || state === 'whole') {
1576
+ state = 'whole';
1577
+ value += c;
1578
+ continue;
1579
+ }
1580
+ if (state === 'decimal_point' || state === 'decimal') {
1581
+ state = 'decimal';
1582
+ value += c;
1583
+ continue;
1584
+ }
1585
+ if (state === 'e' || state === 'exponent_sign' || state === 'exponent') {
1586
+ state = 'exponent';
1587
+ value += c;
1588
+ continue;
1589
+ }
1590
+ }
1591
+ if (c === '.') {
1592
+ if (state === 'none' || state === 'sign' || state === 'whole') {
1593
+ state = 'decimal_point';
1594
+ value += c;
1595
+ continue;
1596
+ }
1597
+ }
1598
+ if (c === 'E' || c === 'e') {
1599
+ if (state === 'whole' || state === 'decimal_point' || state === 'decimal') {
1600
+ state = 'e';
1601
+ value += c;
1602
+ continue;
1603
+ }
1604
+ }
1605
+ break;
1606
+ }
1607
+ const number = Number.parseFloat(value);
1608
+ if (Number.isNaN(number)) {
1609
+ return [cursor, null];
1610
+ }
1611
+ return [i - 1, number];
1612
+ };
1476
1613
  const parse = function (path) {
1477
- let cmd;
1478
- const ret = [];
1614
+ const pathData = [];
1615
+ let command = null;
1479
1616
  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;
1617
+ let argsCount = 0;
1618
+ let canHaveComma = false;
1619
+ let hadComma = false;
1620
+ for (let i = 0; i < path.length; i += 1) {
1621
+ const c = path.charAt(i);
1622
+ if (isWsp(c)) {
1623
+ continue;
1624
+ }
1625
+ if (canHaveComma && c === ',') {
1626
+ if (hadComma) {
1627
+ break;
1628
+ }
1629
+ hadComma = true;
1630
+ continue;
1631
+ }
1632
+ if (isCommand(c)) {
1633
+ if (hadComma) {
1634
+ return pathData;
1635
+ }
1636
+ if (command == null) {
1637
+ if (c !== 'M' && c !== 'm') {
1638
+ return pathData;
1639
+ }
1640
+ } else {
1641
+ if (args.length !== 0) {
1642
+ return pathData;
1489
1643
  }
1490
- ret[ret.length] = {
1491
- cmd,
1492
- args
1493
- };
1494
- args = [];
1495
- curArg = '';
1496
- foundDecimal = false;
1497
- }
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;
1502
1644
  }
1503
- if (args.length === params) {
1504
- ret[ret.length] = {
1505
- cmd,
1645
+ command = c;
1646
+ args = [];
1647
+ argsCount = parameters[command];
1648
+ canHaveComma = false;
1649
+ if (argsCount === 0) {
1650
+ pathData.push({
1651
+ command,
1506
1652
  args
1507
- };
1508
- args = [+curArg];
1509
- if (cmd === 'M') {
1510
- cmd = 'L';
1653
+ });
1654
+ }
1655
+ continue;
1656
+ }
1657
+ if (command == null) {
1658
+ return pathData;
1659
+ }
1660
+ let newCursor = i;
1661
+ let number = null;
1662
+ if (command === 'A' || command === 'a') {
1663
+ const position = args.length;
1664
+ if (position === 0 || position === 1) {
1665
+ if (c !== '+' && c !== '-') {
1666
+ [newCursor, number] = readNumber(path, i);
1511
1667
  }
1512
- if (cmd === 'm') {
1513
- cmd = 'l';
1668
+ }
1669
+ if (position === 2 || position === 5 || position === 6) {
1670
+ [newCursor, number] = readNumber(path, i);
1671
+ }
1672
+ if (position === 3 || position === 4) {
1673
+ if (c === '0') {
1674
+ number = 0;
1675
+ }
1676
+ if (c === '1') {
1677
+ number = 1;
1514
1678
  }
1515
- } else {
1516
- args[args.length] = +curArg;
1517
1679
  }
1518
- foundDecimal = c === '.';
1519
- curArg = ['-', '.'].includes(c) ? c : '';
1520
1680
  } 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,
1681
+ [newCursor, number] = readNumber(path, i);
1682
+ }
1683
+ if (number == null) {
1684
+ return pathData;
1685
+ }
1686
+ args.push(number);
1687
+ canHaveComma = true;
1688
+ hadComma = false;
1689
+ i = newCursor;
1690
+ if (args.length === argsCount) {
1691
+ pathData.push({
1692
+ command,
1531
1693
  args
1532
- };
1533
- args = [+curArg];
1534
- if (cmd === 'M') {
1535
- cmd = 'L';
1694
+ });
1695
+ if (command === 'M') {
1696
+ command = 'L';
1536
1697
  }
1537
- if (cmd === 'm') {
1538
- cmd = 'l';
1698
+ if (command === 'm') {
1699
+ command = 'l';
1539
1700
  }
1540
- } else {
1541
- args[args.length] = +curArg;
1701
+ args = [];
1542
1702
  }
1543
1703
  }
1544
- ret[ret.length] = {
1545
- cmd,
1546
- args
1547
- };
1548
- return ret;
1704
+ return pathData;
1549
1705
  };
1550
1706
  const apply = function (commands, doc) {
1551
1707
  cx = cy = px = py = sx = sy = 0;
1552
1708
  for (let i = 0; i < commands.length; i++) {
1553
1709
  const c = commands[i];
1554
- if (typeof runners[c.cmd] === 'function') {
1555
- runners[c.cmd](doc, c.args);
1710
+ if (typeof runners[c.command] === 'function') {
1711
+ runners[c.command](doc, c.args);
1556
1712
  }
1557
1713
  }
1558
1714
  };
@@ -1862,21 +2018,42 @@ var VectorMixin = {
1862
2018
  rect(x, y, w, h) {
1863
2019
  return this.addContent(`${number$1(x)} ${number$1(y)} ${number$1(w)} ${number$1(h)} re`);
1864
2020
  },
1865
- roundedRect(x, y, w, h, r) {
1866
- if (r == null) {
1867
- r = 0;
1868
- }
1869
- r = Math.min(r, 0.5 * w, 0.5 * h);
1870
- const c = r * (1.0 - KAPPA);
1871
- this.moveTo(x + r, y);
1872
- this.lineTo(x + w - r, y);
1873
- this.bezierCurveTo(x + w - c, y, x + w, y + c, x + w, y + r);
1874
- this.lineTo(x + w, y + h - r);
1875
- this.bezierCurveTo(x + w, y + h - c, x + w - c, y + h, x + w - r, y + h);
1876
- this.lineTo(x + r, y + h);
1877
- this.bezierCurveTo(x + c, y + h, x, y + h - c, x, y + h - r);
1878
- this.lineTo(x, y + r);
1879
- this.bezierCurveTo(x, y + c, x + c, y, x + r, y);
2021
+ roundedRect(x, y, w, h, borderRadius) {
2022
+ if (borderRadius == null) {
2023
+ borderRadius = 0;
2024
+ }
2025
+ let radii;
2026
+ if (Array.isArray(borderRadius)) {
2027
+ radii = borderRadius.slice(0, 4);
2028
+ } else {
2029
+ radii = [borderRadius, borderRadius, borderRadius, borderRadius];
2030
+ }
2031
+ const limit = Math.min(0.5 * w, 0.5 * h);
2032
+ const rTL = Math.max(0, Math.min(radii[0] || 0, limit));
2033
+ const rTR = Math.max(0, Math.min(radii[1] || 0, limit));
2034
+ const rBR = Math.max(0, Math.min(radii[2] || 0, limit));
2035
+ const rBL = Math.max(0, Math.min(radii[3] || 0, limit));
2036
+ const cpTR = rTR * (1.0 - KAPPA);
2037
+ const cpBR = rBR * (1.0 - KAPPA);
2038
+ const cpBL = rBL * (1.0 - KAPPA);
2039
+ const cpTL = rTL * (1.0 - KAPPA);
2040
+ this.moveTo(x + rTL, y);
2041
+ this.lineTo(x + w - rTR, y);
2042
+ if (rTR > 0) {
2043
+ this.bezierCurveTo(x + w - cpTR, y, x + w, y + cpTR, x + w, y + rTR);
2044
+ }
2045
+ this.lineTo(x + w, y + h - rBR);
2046
+ if (rBR > 0) {
2047
+ this.bezierCurveTo(x + w, y + h - cpBR, x + w - cpBR, y + h, x + w - rBR, y + h);
2048
+ }
2049
+ this.lineTo(x + rBL, y + h);
2050
+ if (rBL > 0) {
2051
+ this.bezierCurveTo(x + cpBL, y + h, x, y + h - cpBL, x, y + h - rBL);
2052
+ }
2053
+ this.lineTo(x, y + rTL);
2054
+ if (rTL > 0) {
2055
+ this.bezierCurveTo(x, y + cpTL, x + cpTL, y, x + rTL, y);
2056
+ }
1880
2057
  return this.closePath();
1881
2058
  },
1882
2059
  ellipse(x, y, r1, r2) {
@@ -2168,7 +2345,7 @@ class AFMFont {
2168
2345
  if (match = line.match(/^Start(\w+)/)) {
2169
2346
  section = match[1];
2170
2347
  continue;
2171
- } else if (match = line.match(/^End(\w+)/)) {
2348
+ } else if (line.match(/^End(\w+)/)) {
2172
2349
  section = '';
2173
2350
  continue;
2174
2351
  }
@@ -2512,9 +2689,15 @@ class EmbeddedFont extends PDFFont {
2512
2689
  descriptor.data.FontFile2 = fontFile;
2513
2690
  }
2514
2691
  if (this.document.subset && this.document.subset === 1) {
2515
- const CIDSet = Buffer.from('FFFFFFFFC0', 'hex');
2692
+ const maxCID = this.widths.length - 1;
2693
+ const cidSetBuffer = Buffer.alloc(Math.ceil((maxCID + 1) / 8), 0);
2694
+ for (let cid = 0; cid <= maxCID; cid++) {
2695
+ if (this.widths[cid] != null) {
2696
+ cidSetBuffer[Math.floor(cid / 8)] |= 0x80 >> cid % 8;
2697
+ }
2698
+ }
2516
2699
  const CIDSetRef = this.document.ref();
2517
- CIDSetRef.write(CIDSet);
2700
+ CIDSetRef.write(cidSetBuffer);
2518
2701
  CIDSetRef.end();
2519
2702
  descriptor.data.CIDSet = CIDSetRef;
2520
2703
  }
@@ -2584,7 +2767,7 @@ begincmap
2584
2767
  1 begincodespacerange
2585
2768
  <0000><ffff>
2586
2769
  endcodespacerange
2587
- 1 beginbfrange
2770
+ ${ranges.length} beginbfrange
2588
2771
  ${ranges.join('\n')}
2589
2772
  endbfrange
2590
2773
  endcmap
@@ -2676,9 +2859,12 @@ var FontsMixin = {
2676
2859
  if (cacheKey) {
2677
2860
  this._fontFamilies[cacheKey] = this._font;
2678
2861
  }
2679
- if (this._font.name) {
2862
+ if (this._font.name && !this._fontFamilies[this._font.name]) {
2680
2863
  this._fontFamilies[this._font.name] = this._font;
2681
2864
  }
2865
+ if (!cacheKey && (!this._font.name || this._fontFamilies[this._font.name] !== this._font)) {
2866
+ this._fontFamilies[this._font.id] = this._font;
2867
+ }
2682
2868
  return this;
2683
2869
  },
2684
2870
  fontSize(_fontSize) {
@@ -2757,12 +2943,13 @@ var FontsMixin = {
2757
2943
 
2758
2944
  const SOFT_HYPHEN = '\u00AD';
2759
2945
  const HYPHEN = '-';
2760
- class LineWrapper extends events.EventEmitter {
2946
+ class LineWrapper {
2761
2947
  constructor(document, options) {
2762
- super();
2948
+ this._listeners = Object.create(null);
2763
2949
  this.document = document;
2764
2950
  this.horizontalScaling = options.horizontalScaling || 100;
2765
2951
  this.indent = (options.indent || 0) * this.horizontalScaling / 100;
2952
+ this.indentAllLines = options.indentAllLines || false;
2766
2953
  this.characterSpacing = (options.characterSpacing || 0) * this.horizontalScaling / 100;
2767
2954
  this.wordSpacing = (options.wordSpacing === 0) * this.horizontalScaling / 100;
2768
2955
  this.columns = options.columns || 1;
@@ -2814,6 +3001,22 @@ class LineWrapper extends events.EventEmitter {
2814
3001
  });
2815
3002
  });
2816
3003
  }
3004
+ on(event, listener) {
3005
+ (this._listeners[event] || (this._listeners[event] = [])).push(listener);
3006
+ }
3007
+ once(event, listener) {
3008
+ const wrapper = (...args) => {
3009
+ const listeners = this._listeners[event];
3010
+ listeners.splice(listeners.indexOf(wrapper), 1);
3011
+ listener(...args);
3012
+ };
3013
+ this.on(event, wrapper);
3014
+ }
3015
+ emit(event, ...args) {
3016
+ const listeners = this._listeners[event];
3017
+ if (!listeners) return;
3018
+ for (const listener of listeners.slice()) listener(...args);
3019
+ }
2817
3020
  wordWidth(word) {
2818
3021
  return PDFNumber(this.document.widthOfString(word, this) + this.characterSpacing + this.wordSpacing);
2819
3022
  }
@@ -2879,6 +3082,9 @@ class LineWrapper extends events.EventEmitter {
2879
3082
  }
2880
3083
  }
2881
3084
  wrap(text, options) {
3085
+ const {
3086
+ document
3087
+ } = this;
2882
3088
  this.horizontalScaling = options.horizontalScaling || 100;
2883
3089
  if (options.indent != null) {
2884
3090
  this.indent = options.indent * this.horizontalScaling / 100;
@@ -2892,24 +3098,20 @@ class LineWrapper extends events.EventEmitter {
2892
3098
  if (options.ellipsis != null) {
2893
3099
  this.ellipsis = options.ellipsis;
2894
3100
  }
2895
- const nextY = this.document.y + this.document.currentLineHeight(true);
2896
- if (this.document.y > this.maxY || nextY > this.maxY) {
3101
+ const nextY = document.y + document.currentLineHeight(true);
3102
+ if (document.y > this.maxY || nextY > this.maxY) {
2897
3103
  this.nextSection();
2898
3104
  }
2899
3105
  let buffer = '';
2900
3106
  let textWidth = 0;
2901
3107
  let wc = 0;
2902
3108
  let lc = 0;
2903
- let {
2904
- y
2905
- } = this.document;
3109
+ let continueY = document.y;
2906
3110
  const emitLine = () => {
2907
3111
  options.textWidth = textWidth + this.wordSpacing * (wc - 1);
2908
3112
  options.wordCount = wc;
2909
3113
  options.lineWidth = this.lineWidth;
2910
- ({
2911
- y
2912
- } = this.document);
3114
+ continueY = document.y;
2913
3115
  this.emit('line', buffer, options, this);
2914
3116
  return lc++;
2915
3117
  };
@@ -2925,8 +3127,8 @@ class LineWrapper extends events.EventEmitter {
2925
3127
  wc++;
2926
3128
  }
2927
3129
  if (bk.required || !this.canFit(word, w)) {
2928
- const lh = this.document.currentLineHeight(true);
2929
- if (this.height != null && this.ellipsis && PDFNumber(this.document.y + lh * 2) > this.maxY && this.column >= this.columns) {
3130
+ const lh = document.currentLineHeight(true);
3131
+ if (this.height != null && this.ellipsis && PDFNumber(document.y + lh * 2) > this.maxY && this.column >= this.columns) {
2930
3132
  if (this.ellipsis === true) {
2931
3133
  this.ellipsis = '…';
2932
3134
  }
@@ -2955,7 +3157,7 @@ class LineWrapper extends events.EventEmitter {
2955
3157
  this.spaceLeft -= this.wordWidth(HYPHEN);
2956
3158
  }
2957
3159
  emitLine();
2958
- if (PDFNumber(this.document.y + lh) > this.maxY) {
3160
+ if (PDFNumber(document.y + lh) > this.maxY) {
2959
3161
  this.emit('sectionEnd', options, this);
2960
3162
  const shouldContinue = this.nextSection();
2961
3163
  if (!shouldContinue) {
@@ -2990,9 +3192,9 @@ class LineWrapper extends events.EventEmitter {
2990
3192
  this.continuedX = 0;
2991
3193
  }
2992
3194
  this.continuedX += options.textWidth || 0;
2993
- this.document.y = y;
3195
+ document.y = continueY;
2994
3196
  } else {
2995
- this.document.x = this.startX;
3197
+ document.x = this.startX;
2996
3198
  }
2997
3199
  }
2998
3200
  nextSection(options) {
@@ -3004,7 +3206,13 @@ class LineWrapper extends events.EventEmitter {
3004
3206
  this.column = 1;
3005
3207
  this.startY = this.document.page.margins.top;
3006
3208
  this.maxY = this.document.page.maxY();
3007
- this.document.x = this.startX;
3209
+ if (this.indentAllLines) {
3210
+ const indent = this.continuedX || this.indent;
3211
+ this.document.x += indent;
3212
+ this.lineWidth -= indent;
3213
+ } else {
3214
+ this.document.x = this.startX;
3215
+ }
3008
3216
  if (this.document._fillColor) {
3009
3217
  this.document.fillColor(...this.document._fillColor);
3010
3218
  }
@@ -3301,7 +3509,7 @@ var TextMixin = {
3301
3509
  return this;
3302
3510
  },
3303
3511
  _initOptions(x = {}, y, options = {}) {
3304
- if (typeof x === 'object') {
3512
+ if (x && typeof x === 'object') {
3305
3513
  options = x;
3306
3514
  x = null;
3307
3515
  }
@@ -3334,7 +3542,7 @@ var TextMixin = {
3334
3542
  if (result.columnGap == null) {
3335
3543
  result.columnGap = 18;
3336
3544
  }
3337
- result.rotation = Number(options.rotation ?? 0) % 360;
3545
+ result.rotation = Number(result.rotation ?? 0) % 360;
3338
3546
  if (result.rotation < 0) result.rotation += 360;
3339
3547
  return result;
3340
3548
  },
@@ -3408,7 +3616,11 @@ var TextMixin = {
3408
3616
  }
3409
3617
  const renderedWidth = options.textWidth + wordSpacing * (options.wordCount - 1) + characterSpacing * (text.length - 1);
3410
3618
  if (options.link != null) {
3411
- this.link(x, y, renderedWidth, this.currentLineHeight(), options.link);
3619
+ const linkOptions = {};
3620
+ if (this._currentStructureElement && this._currentStructureElement.dictionary.data.S === 'Link') {
3621
+ linkOptions.structParent = this._currentStructureElement;
3622
+ }
3623
+ this.link(x, y, renderedWidth, this.currentLineHeight(), options.link, linkOptions);
3412
3624
  }
3413
3625
  if (options.goTo != null) {
3414
3626
  this.goTo(x, y, renderedWidth, this.currentLineHeight(), options.goTo);
@@ -3537,6 +3749,61 @@ var TextMixin = {
3537
3749
  }
3538
3750
  };
3539
3751
 
3752
+ const toBinaryString = bytes => {
3753
+ const chunkSize = 0x8000;
3754
+ let out = '';
3755
+ for (let i = 0; i < bytes.length; i += chunkSize) {
3756
+ const end = Math.min(i + chunkSize, bytes.length);
3757
+ out += String.fromCharCode.apply(null, bytes.subarray(i, end));
3758
+ }
3759
+ return out;
3760
+ };
3761
+ const readUInt16BE = (bytes, offset = 0) => (bytes[offset] << 8 | bytes[offset + 1]) >>> 0;
3762
+ const readUInt16LE = (bytes, offset = 0) => (bytes[offset + 1] << 8 | bytes[offset]) >>> 0;
3763
+ const readUInt32BE = (bytes, offset = 0) => bytes[offset] * 0x1000000 + (bytes[offset + 1] << 16 | bytes[offset + 2] << 8 | bytes[offset + 3]) >>> 0;
3764
+ const readUInt32LE = (bytes, offset = 0) => (bytes[offset] | bytes[offset + 1] << 8 | bytes[offset + 2] << 16) + bytes[offset + 3] * 0x1000000 >>> 0;
3765
+
3766
+ const parseExifOrientation = data => {
3767
+ if (!data || data.length < 20) return null;
3768
+ let pos = 2;
3769
+ while (pos < data.length - 4) {
3770
+ while (pos < data.length && data[pos] !== 0xff) pos++;
3771
+ if (pos >= data.length - 4) return null;
3772
+ const marker = readUInt16BE(data, pos);
3773
+ pos += 2;
3774
+ if (marker === 0xffda) return null;
3775
+ if (marker >= 0xffd0 && marker <= 0xffd9 || marker === 0xff01) continue;
3776
+ if (pos + 2 > data.length) return null;
3777
+ const segmentLength = readUInt16BE(data, pos);
3778
+ if (marker === 0xffe1 && pos + 8 <= data.length) {
3779
+ const exifHeader = toBinaryString(data.subarray(pos + 2, pos + 8));
3780
+ if (exifHeader === 'Exif\x00\x00') {
3781
+ const tiffStart = pos + 8;
3782
+ if (tiffStart + 8 > data.length) return null;
3783
+ const byteOrder = toBinaryString(data.subarray(tiffStart, tiffStart + 2));
3784
+ const isLittleEndian = byteOrder === 'II';
3785
+ if (!isLittleEndian && byteOrder !== 'MM') return null;
3786
+ const read16 = isLittleEndian ? o => readUInt16LE(data, o) : o => readUInt16BE(data, o);
3787
+ const read32 = isLittleEndian ? o => readUInt32LE(data, o) : o => readUInt32BE(data, o);
3788
+ if (read16(tiffStart + 2) !== 42) return null;
3789
+ const ifdPos = tiffStart + read32(tiffStart + 4);
3790
+ if (ifdPos + 2 > data.length) return null;
3791
+ const entryCount = read16(ifdPos);
3792
+ for (let i = 0; i < entryCount; i++) {
3793
+ const entryPos = ifdPos + 2 + i * 12;
3794
+ if (entryPos + 12 > data.length) return null;
3795
+ if (read16(entryPos) === 0x0112) {
3796
+ const value = read16(entryPos + 8);
3797
+ return value >= 1 && value <= 8 ? value : null;
3798
+ }
3799
+ }
3800
+ return null;
3801
+ }
3802
+ }
3803
+ pos += segmentLength;
3804
+ }
3805
+ return null;
3806
+ };
3540
3807
  const MARKERS = [0xffc0, 0xffc1, 0xffc2, 0xffc3, 0xffc5, 0xffc6, 0xffc7, 0xffc8, 0xffc9, 0xffca, 0xffcb, 0xffcc, 0xffcd, 0xffce, 0xffcf];
3541
3808
  const COLOR_SPACE_MAP = {
3542
3809
  1: 'DeviceGray',
@@ -3551,9 +3818,11 @@ class JPEG {
3551
3818
  if (this.data.readUInt16BE(0) !== 0xffd8) {
3552
3819
  throw 'SOI not found in JPEG';
3553
3820
  }
3554
- this.orientation = exif.fromBuffer(this.data).Orientation || 1;
3821
+ this.orientation = parseExifOrientation(this.data) || 1;
3555
3822
  let pos = 2;
3556
3823
  while (pos < this.data.length) {
3824
+ while (pos < this.data.length && this.data[pos] !== 0xff) pos++;
3825
+ if (pos >= this.data.length) break;
3557
3826
  marker = this.data.readUInt16BE(pos);
3558
3827
  pos += 2;
3559
3828
  if (MARKERS.includes(marker)) {
@@ -3570,7 +3839,7 @@ class JPEG {
3570
3839
  pos += 2;
3571
3840
  this.width = this.data.readUInt16BE(pos);
3572
3841
  pos += 2;
3573
- const channels = this.data[pos++];
3842
+ const channels = this.data[pos + 1];
3574
3843
  this.colorSpace = COLOR_SPACE_MAP[channels];
3575
3844
  this.obj = null;
3576
3845
  }
@@ -3605,58 +3874,60 @@ class PNGImage {
3605
3874
  this.obj = null;
3606
3875
  }
3607
3876
  embed(document) {
3608
- let dataDecoded = false;
3609
3877
  this.document = document;
3610
3878
  if (this.obj) {
3611
3879
  return;
3612
3880
  }
3613
- const hasAlphaChannel = this.image.hasAlphaChannel;
3614
- const isInterlaced = this.image.interlaceMethod === 1;
3615
- this.obj = this.document.ref({
3881
+ const {
3882
+ image,
3883
+ height,
3884
+ width
3885
+ } = this;
3886
+ const hasAlphaChannel = image.hasAlphaChannel;
3887
+ const isInterlaced = image.interlaceMethod === 1;
3888
+ const obj = this.obj = document.ref({
3616
3889
  Type: 'XObject',
3617
3890
  Subtype: 'Image',
3618
- BitsPerComponent: hasAlphaChannel ? 8 : this.image.bits,
3619
- Width: this.width,
3620
- Height: this.height,
3891
+ BitsPerComponent: hasAlphaChannel ? 8 : image.bits,
3892
+ Width: width,
3893
+ Height: height,
3621
3894
  Filter: 'FlateDecode'
3622
3895
  });
3623
3896
  if (!hasAlphaChannel) {
3624
- const params = this.document.ref({
3897
+ const params = document.ref({
3625
3898
  Predictor: isInterlaced ? 1 : 15,
3626
- Colors: this.image.colors,
3627
- BitsPerComponent: this.image.bits,
3628
- Columns: this.width
3899
+ Colors: image.colors,
3900
+ BitsPerComponent: image.bits,
3901
+ Columns: width
3629
3902
  });
3630
- this.obj.data['DecodeParms'] = params;
3903
+ obj.data['DecodeParms'] = params;
3631
3904
  params.end();
3632
3905
  }
3633
- if (this.image.palette.length === 0) {
3634
- this.obj.data['ColorSpace'] = this.image.colorSpace;
3906
+ if (image.palette.length === 0) {
3907
+ obj.data['ColorSpace'] = image.colorSpace;
3635
3908
  } else {
3636
- const palette = this.document.ref();
3637
- palette.end(Buffer.from(this.image.palette));
3638
- this.obj.data['ColorSpace'] = ['Indexed', 'DeviceRGB', this.image.palette.length / 3 - 1, palette];
3639
- }
3640
- if (this.image.transparency.grayscale != null) {
3641
- const val = this.image.transparency.grayscale;
3642
- this.obj.data['Mask'] = [val, val];
3643
- } else if (this.image.transparency.rgb) {
3909
+ const palette = document.ref();
3910
+ palette.end(Buffer.from(image.palette));
3911
+ obj.data['ColorSpace'] = ['Indexed', 'DeviceRGB', image.palette.length / 3 - 1, palette];
3912
+ }
3913
+ if (image.transparency.grayscale != null) {
3914
+ const val = image.transparency.grayscale;
3915
+ obj.data['Mask'] = [val, val];
3916
+ } else if (image.transparency.rgb) {
3644
3917
  const {
3645
3918
  rgb
3646
- } = this.image.transparency;
3919
+ } = image.transparency;
3647
3920
  const mask = [];
3648
3921
  for (let x of rgb) {
3649
3922
  mask.push(x, x);
3650
3923
  }
3651
- this.obj.data['Mask'] = mask;
3652
- } else if (this.image.transparency.indexed) {
3653
- dataDecoded = true;
3924
+ obj.data['Mask'] = mask;
3925
+ } else if (image.transparency.indexed) {
3654
3926
  return this.loadIndexedAlphaChannel();
3655
3927
  } else if (hasAlphaChannel) {
3656
- dataDecoded = true;
3657
3928
  return this.splitAlphaChannel();
3658
3929
  }
3659
- if (isInterlaced && !dataDecoded) {
3930
+ if (isInterlaced && true) {
3660
3931
  return this.decodeData();
3661
3932
  }
3662
3933
  this.finalize();
@@ -3705,12 +3976,16 @@ class PNGImage {
3705
3976
  }
3706
3977
  loadIndexedAlphaChannel() {
3707
3978
  const transparency = this.image.transparency.indexed;
3979
+ const isInterlaced = this.image.interlaceMethod === 1;
3708
3980
  return this.image.decodePixels(pixels => {
3709
3981
  const alphaChannel = Buffer.alloc(this.width * this.height);
3710
3982
  let i = 0;
3711
3983
  for (let j = 0, end = pixels.length; j < end; j++) {
3712
3984
  alphaChannel[i++] = transparency[pixels[j]];
3713
3985
  }
3986
+ if (isInterlaced) {
3987
+ this.imgData = zlib.deflateSync(Buffer.from(pixels));
3988
+ }
3714
3989
  this.alphaChannel = zlib.deflateSync(alphaChannel);
3715
3990
  return this.finalize();
3716
3991
  });
@@ -3743,7 +4018,7 @@ class PDFImage {
3743
4018
  }
3744
4019
  if (data[0] === 0xff && data[1] === 0xd8) {
3745
4020
  return new JPEG(data, label);
3746
- } else if (data[0] === 0x89 && data.toString('ascii', 1, 4) === 'PNG') {
4021
+ } else if (data[0] === 0x89 && data[1] === 0x50 && data[2] === 0x4e && data[3] === 0x47) {
3747
4022
  return new PNGImage(data, label);
3748
4023
  } else {
3749
4024
  throw new Error('Unknown image format.');
@@ -3914,6 +4189,9 @@ var ImagesMixin = {
3914
4189
  this.y += h;
3915
4190
  }
3916
4191
  this.save();
4192
+ if (options.opacity != null) {
4193
+ this._doOpacity(options.opacity, null);
4194
+ }
3917
4195
  if (rotateAngle) {
3918
4196
  this.rotate(rotateAngle, {
3919
4197
  origin: [originX, originY]
@@ -3939,6 +4217,12 @@ var ImagesMixin = {
3939
4217
  }
3940
4218
  };
3941
4219
 
4220
+ class PDFAnnotationReference {
4221
+ constructor(annotationRef) {
4222
+ this.annotationRef = annotationRef;
4223
+ }
4224
+ }
4225
+
3942
4226
  var AnnotationsMixin = {
3943
4227
  annotate(x, y, w, h, options) {
3944
4228
  options.Type = 'Annot';
@@ -3956,12 +4240,18 @@ var AnnotationsMixin = {
3956
4240
  if (typeof options.Dest === 'string') {
3957
4241
  options.Dest = new String(options.Dest);
3958
4242
  }
4243
+ const structParent = options.structParent;
4244
+ delete options.structParent;
3959
4245
  for (let key in options) {
3960
4246
  const val = options[key];
3961
4247
  options[key[0].toUpperCase() + key.slice(1)] = val;
3962
4248
  }
3963
4249
  const ref = this.ref(options);
3964
4250
  this.page.annotations.push(ref);
4251
+ if (structParent && typeof structParent.add === 'function') {
4252
+ const annotRef = new PDFAnnotationReference(ref);
4253
+ structParent.add(annotRef);
4254
+ }
3965
4255
  ref.end();
3966
4256
  return this;
3967
4257
  },
@@ -4005,6 +4295,9 @@ var AnnotationsMixin = {
4005
4295
  });
4006
4296
  options.A.end();
4007
4297
  }
4298
+ if (options.structParent && !options.Contents) {
4299
+ options.Contents = new String('');
4300
+ }
4008
4301
  return this.annotate(x, y, w, h, options);
4009
4302
  },
4010
4303
  _markup(x, y, w, h, options = {}) {
@@ -4076,15 +4369,26 @@ var AnnotationsMixin = {
4076
4369
  }
4077
4370
  };
4078
4371
 
4372
+ const DEFAULT_OPTIONS = {
4373
+ top: 0,
4374
+ left: 0,
4375
+ zoom: 0,
4376
+ fit: true,
4377
+ pageNumber: null,
4378
+ expanded: false
4379
+ };
4079
4380
  class PDFOutline {
4080
- constructor(document, parent, title, dest, options = {
4081
- expanded: false
4082
- }) {
4381
+ constructor(document, parent, title, dest, options = DEFAULT_OPTIONS) {
4083
4382
  this.document = document;
4084
4383
  this.options = options;
4085
4384
  this.outlineData = {};
4086
4385
  if (dest !== null) {
4087
- this.outlineData['Dest'] = [dest.dictionary, 'Fit'];
4386
+ const destWidth = dest.data.MediaBox[2];
4387
+ const destHeight = dest.data.MediaBox[3];
4388
+ const top = destHeight - (options.top || 0);
4389
+ const left = destWidth - (options.left || 0);
4390
+ const zoom = options.zoom || 0;
4391
+ this.outlineData['Dest'] = options.fit ? [dest, 'Fit'] : [dest, 'XYZ', left, top, zoom];
4088
4392
  }
4089
4393
  if (parent !== null) {
4090
4394
  this.outlineData['Parent'] = parent;
@@ -4095,10 +4399,10 @@ class PDFOutline {
4095
4399
  this.dictionary = this.document.ref(this.outlineData);
4096
4400
  this.children = [];
4097
4401
  }
4098
- addItem(title, options = {
4099
- expanded: false
4100
- }) {
4101
- const result = new PDFOutline(this.document, this.dictionary, title, this.document.page, options);
4402
+ addItem(title, options = DEFAULT_OPTIONS) {
4403
+ const pages = this.document._root.data.Pages.data.Kids;
4404
+ const dest = options.pageNumber != null ? pages[options.pageNumber] : this.document.page.dictionary;
4405
+ const result = new PDFOutline(this.document, this.dictionary, title, dest, options);
4102
4406
  this.children.push(result);
4103
4407
  return result;
4104
4408
  }
@@ -4134,7 +4438,7 @@ var OutlineMixin = {
4134
4438
  this.outline.endOutline();
4135
4439
  if (this.outline.children.length > 0) {
4136
4440
  this._root.data.Outlines = this.outline.dictionary;
4137
- return this._root.data.PageMode = 'UseOutlines';
4441
+ return this._root.data.PageMode = this._root.data.PageMode || 'UseOutlines';
4138
4442
  }
4139
4443
  }
4140
4444
  };
@@ -4165,21 +4469,41 @@ class PDFStructureElement {
4165
4469
  children = options;
4166
4470
  options = {};
4167
4471
  }
4168
- if (typeof options.title !== 'undefined') {
4472
+ if (options.title) {
4169
4473
  data.T = new String(options.title);
4170
4474
  }
4171
- if (typeof options.lang !== 'undefined') {
4475
+ if (options.lang) {
4172
4476
  data.Lang = new String(options.lang);
4173
4477
  }
4174
- if (typeof options.alt !== 'undefined') {
4478
+ if (options.alt) {
4175
4479
  data.Alt = new String(options.alt);
4176
4480
  }
4177
- if (typeof options.expanded !== 'undefined') {
4481
+ if (options.expanded) {
4178
4482
  data.E = new String(options.expanded);
4179
4483
  }
4180
- if (typeof options.actual !== 'undefined') {
4484
+ if (options.actual) {
4181
4485
  data.ActualText = new String(options.actual);
4182
4486
  }
4487
+ const hasBbox = Array.isArray(options.bbox) && options.bbox.length === 4;
4488
+ const hasPlacement = typeof options.placement === 'string';
4489
+ if (hasBbox || hasPlacement) {
4490
+ const attrs = {
4491
+ O: 'Layout'
4492
+ };
4493
+ attrs.Placement = hasPlacement ? options.placement : 'Block';
4494
+ if (hasBbox) {
4495
+ const height = document.page.height;
4496
+ attrs.BBox = [options.bbox[0], height - options.bbox[3], options.bbox[2], height - options.bbox[1]];
4497
+ }
4498
+ data.A = attrs;
4499
+ }
4500
+ if (options.scope) {
4501
+ data.A = {
4502
+ ...(data.A || {}),
4503
+ O: 'Table',
4504
+ Scope: options.scope
4505
+ };
4506
+ }
4183
4507
  this._children = [];
4184
4508
  if (children) {
4185
4509
  if (!Array.isArray(children)) {
@@ -4205,6 +4529,9 @@ class PDFStructureElement {
4205
4529
  if (child instanceof PDFStructureContent) {
4206
4530
  this._addContentToParentTree(child);
4207
4531
  }
4532
+ if (child instanceof PDFAnnotationReference) {
4533
+ this._addAnnotationToParentTree(child.annotationRef);
4534
+ }
4208
4535
  if (typeof child === 'function' && this._attached) {
4209
4536
  child = this._contentForClosure(child);
4210
4537
  }
@@ -4220,6 +4547,12 @@ class PDFStructureElement {
4220
4547
  pageStructParents[mcid] = this.dictionary;
4221
4548
  });
4222
4549
  }
4550
+ _addAnnotationToParentTree(annotRef) {
4551
+ const parentTreeKey = this.document.createStructParentTreeNextKey();
4552
+ annotRef.data.StructParent = parentTreeKey;
4553
+ const parentTree = this.document.getStructParentTree();
4554
+ parentTree.add(parentTreeKey, this.dictionary);
4555
+ }
4223
4556
  setParent(parentRef) {
4224
4557
  if (this.dictionary.data.P) {
4225
4558
  throw new Error(`Structure element added to more than one parent`);
@@ -4251,11 +4584,17 @@ class PDFStructureElement {
4251
4584
  this._flush();
4252
4585
  }
4253
4586
  _isValidChild(child) {
4254
- return child instanceof PDFStructureElement || child instanceof PDFStructureContent || typeof child === 'function';
4587
+ return child instanceof PDFStructureElement || child instanceof PDFStructureContent || child instanceof PDFAnnotationReference || typeof child === 'function';
4255
4588
  }
4256
4589
  _contentForClosure(closure) {
4257
4590
  const content = this.document.markStructureContent(this.dictionary.data.S);
4591
+ const prevStructElement = this.document._currentStructureElement;
4592
+ this.document._currentStructureElement = this;
4593
+ const wasEnded = this._ended;
4594
+ this._ended = false;
4258
4595
  closure();
4596
+ this._ended = wasEnded;
4597
+ this.document._currentStructureElement = prevStructElement;
4259
4598
  this.document.endMarkedContent();
4260
4599
  this._addContentToParentTree(content);
4261
4600
  return content;
@@ -4308,6 +4647,15 @@ class PDFStructureElement {
4308
4647
  }
4309
4648
  });
4310
4649
  }
4650
+ if (child instanceof PDFAnnotationReference) {
4651
+ const pageRef = this.document.page.dictionary;
4652
+ const objr = {
4653
+ Type: 'OBJR',
4654
+ Obj: child.annotationRef,
4655
+ Pg: pageRef
4656
+ };
4657
+ this.dictionary.data.K.push(objr);
4658
+ }
4311
4659
  }
4312
4660
  }
4313
4661
 
@@ -4401,6 +4749,13 @@ var MarkingsMixin = {
4401
4749
  endMarkedContent() {
4402
4750
  this.page.markings.pop();
4403
4751
  this.addContent('EMC');
4752
+ if (this._textOptions) {
4753
+ delete this._textOptions.link;
4754
+ delete this._textOptions.goTo;
4755
+ delete this._textOptions.destination;
4756
+ delete this._textOptions.underline;
4757
+ delete this._textOptions.strike;
4758
+ }
4404
4759
  return this;
4405
4760
  },
4406
4761
  struct(type, options = {}, children = null) {
@@ -4822,7 +5177,7 @@ var AttachmentsMixin = {
4822
5177
  const match = /^data:(.*?);base64,(.*)$/.exec(src);
4823
5178
  if (match) {
4824
5179
  if (match[1]) {
4825
- refBody.Subtype = match[1].replace('/', '#2F');
5180
+ refBody.Subtype = escapeName(match[1]);
4826
5181
  }
4827
5182
  data = Buffer.from(match[2], 'base64');
4828
5183
  } else {
@@ -4845,9 +5200,9 @@ var AttachmentsMixin = {
4845
5200
  refBody.Params.ModDate = options.modifiedDate;
4846
5201
  }
4847
5202
  if (options.type) {
4848
- refBody.Subtype = options.type.replace('/', '#2F');
5203
+ refBody.Subtype = escapeName(options.type);
4849
5204
  }
4850
- const checksum = CryptoJS.MD5(CryptoJS.lib.WordArray.create(new Uint8Array(data)));
5205
+ const checksum = md5Hex(new Uint8Array(data));
4851
5206
  refBody.Params.CheckSum = new String(checksum);
4852
5207
  refBody.Params.Size = data.byteLength;
4853
5208
  let ref;
@@ -5490,7 +5845,7 @@ function renderRow(row, rowIndex) {
5490
5845
  function renderCell(cell, rowStruct) {
5491
5846
  const cellRenderer = () => {
5492
5847
  if (cell.backgroundColor != null) {
5493
- this.document.save().rect(cell.x, cell.y, cell.width, cell.height).fill(cell.backgroundColor).restore();
5848
+ this.document.save().fillColor(cell.backgroundColor).rect(cell.x, cell.y, cell.width, cell.height).fill().restore();
5494
5849
  }
5495
5850
  renderBorder.call(this, cell.border, cell.borderColor, cell.x, cell.y, cell.width, cell.height);
5496
5851
  if (cell.debug) {
@@ -5551,20 +5906,20 @@ function renderBorder(border, borderColor, x, y, width, height, mask) {
5551
5906
  const doc = this.document;
5552
5907
  if ([border.right, border.bottom, border.left].every(val => val === border.top)) {
5553
5908
  if (border.top > 0) {
5554
- doc.save().lineWidth(border.top).rect(x, y, width, height).stroke(borderColor.top).restore();
5909
+ doc.save().lineWidth(border.top).strokeColor(borderColor.top).rect(x, y, width, height).stroke().restore();
5555
5910
  }
5556
5911
  } else {
5557
5912
  if (border.top > 0) {
5558
- doc.save().lineWidth(border.top).moveTo(x, y).lineTo(x + width, y).stroke(borderColor.top).restore();
5913
+ doc.save().lineWidth(border.top).moveTo(x, y).strokeColor(borderColor.top).lineTo(x + width, y).stroke().restore();
5559
5914
  }
5560
5915
  if (border.right > 0) {
5561
- doc.save().lineWidth(border.right).moveTo(x + width, y).lineTo(x + width, y + height).stroke(borderColor.right).restore();
5916
+ doc.save().lineWidth(border.right).moveTo(x + width, y).strokeColor(borderColor.right).lineTo(x + width, y + height).stroke().restore();
5562
5917
  }
5563
5918
  if (border.bottom > 0) {
5564
- doc.save().lineWidth(border.bottom).moveTo(x + width, y + height).lineTo(x, y + height).stroke(borderColor.bottom).restore();
5919
+ doc.save().lineWidth(border.bottom).moveTo(x + width, y + height).strokeColor(borderColor.bottom).lineTo(x, y + height).stroke().restore();
5565
5920
  }
5566
5921
  if (border.left > 0) {
5567
- doc.save().lineWidth(border.left).moveTo(x, y + height).lineTo(x, y).stroke(borderColor.left).restore();
5922
+ doc.save().lineWidth(border.left).moveTo(x, y + height).strokeColor(borderColor.left).lineTo(x, y).stroke().restore();
5568
5923
  }
5569
5924
  }
5570
5925
  }
@@ -5700,7 +6055,7 @@ var MetadataMixin = {
5700
6055
  }
5701
6056
  this.appendXML(`
5702
6057
  <rdf:Description rdf:about="" xmlns:pdf="http://ns.adobe.com/pdf/1.3/">
5703
- <pdf:Producer>${this.info.Creator}</pdf:Producer>`, false);
6058
+ <pdf:Producer>${this.info.Producer}</pdf:Producer>`, false);
5704
6059
  if (this.info.Keywords) {
5705
6060
  this.appendXML(`
5706
6061
  <pdf:Keywords>${this.info.Keywords}</pdf:Keywords>`, false);
@@ -5771,6 +6126,10 @@ class PDFDocument extends stream.Readable {
5771
6126
  if (this.options.lang) {
5772
6127
  this._root.data.Lang = new String(this.options.lang);
5773
6128
  }
6129
+ if (this.options.pageLayout) {
6130
+ const layout = this.options.pageLayout;
6131
+ this._root.data.PageLayout = layout.charAt(0).toUpperCase() + layout.slice(1);
6132
+ }
5774
6133
  this.page = null;
5775
6134
  this.initMetadata();
5776
6135
  this.initColor();