@kittl/pdfkit 0.17.3 → 0.17.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/js/pdfkit.es.js CHANGED
@@ -1,11 +1,13 @@
1
1
  import stream from 'stream';
2
2
  import zlib from 'zlib';
3
- import CryptoJS from 'crypto-js';
3
+ import { concatBytes } from '@noble/hashes/utils';
4
+ import md5 from 'js-md5';
5
+ import { sha256 } from '@noble/hashes/sha256';
6
+ import { cbc, ecb } from '@noble/ciphers/aes';
4
7
  import fs from 'fs';
5
8
  import * as fontkit from 'fontkit';
6
9
  import { EventEmitter } from 'events';
7
10
  import LineBreaker from 'linebreak';
8
- import exif from 'jpeg-exif';
9
11
  import PNG from 'png-js';
10
12
 
11
13
  class PDFAbstractReference {
@@ -363,6 +365,7 @@ class PDFPage {
363
365
  this._options = options;
364
366
  this.size = options.size || 'letter';
365
367
  this.layout = options.layout || 'portrait';
368
+ this.userUnit = options.userUnit || 1.0;
366
369
  const dimensions = Array.isArray(this.size) ? this.size : SIZES[this.size.toUpperCase()];
367
370
  this.width = dimensions[this.layout === 'portrait' ? 0 : 1];
368
371
  this.height = dimensions[this.layout === 'portrait' ? 1 : 0];
@@ -378,7 +381,8 @@ class PDFPage {
378
381
  Parent: this.document._root.data.Pages,
379
382
  MediaBox: [0, 0, this.width, this.height],
380
383
  Contents: this.content,
381
- Resources: this.resources
384
+ Resources: this.resources,
385
+ UserUnit: this.userUnit
382
386
  });
383
387
  this.markings = [];
384
388
  }
@@ -434,6 +438,7 @@ class PDFPage {
434
438
  for (let color of Object.values(this.document.spotColors)) {
435
439
  this.resources.data.ColorSpace[color.id] = color;
436
440
  }
441
+ this.document._writeSpaceToResources(this.resources);
437
442
  this.resources.end();
438
443
  return this.content.end();
439
444
  }
@@ -451,6 +456,59 @@ class PDFNameTree extends PDFTree {
451
456
  }
452
457
  }
453
458
 
459
+ function md5Hash(data) {
460
+ return new Uint8Array(md5.arrayBuffer(data));
461
+ }
462
+ function md5Hex(data) {
463
+ return md5(data);
464
+ }
465
+
466
+ function sha256Hash(data) {
467
+ return sha256(data);
468
+ }
469
+
470
+ function aesCbcEncrypt(data, key, iv) {
471
+ let padding = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true;
472
+ return cbc(key, iv, {
473
+ disablePadding: !padding
474
+ }).encrypt(data);
475
+ }
476
+ function aesEcbEncrypt(data, key) {
477
+ return ecb(key, {
478
+ disablePadding: true
479
+ }).encrypt(data);
480
+ }
481
+
482
+ function rc4(data, key) {
483
+ const s = new Uint8Array(256);
484
+ for (let i = 0; i < 256; i++) {
485
+ s[i] = i;
486
+ }
487
+ let j = 0;
488
+ for (let i = 0; i < 256; i++) {
489
+ j = j + s[i] + key[i % key.length] & 0xff;
490
+ [s[i], s[j]] = [s[j], s[i]];
491
+ }
492
+ const output = new Uint8Array(data.length);
493
+ for (let i = 0, j = 0, k = 0; k < data.length; k++) {
494
+ i = i + 1 & 0xff;
495
+ j = j + s[i] & 0xff;
496
+ [s[i], s[j]] = [s[j], s[i]];
497
+ output[k] = data[k] ^ s[s[i] + s[j] & 0xff];
498
+ }
499
+ return output;
500
+ }
501
+
502
+ function randomBytes(length) {
503
+ const bytes = new Uint8Array(length);
504
+ if (globalThis.crypto?.getRandomValues) {
505
+ globalThis.crypto.getRandomValues(bytes);
506
+ } else {
507
+ require('crypto').randomFillSync(bytes);
508
+ }
509
+ return bytes;
510
+ }
511
+
454
512
  function inRange(value, rangeGroup) {
455
513
  if (value < rangeGroup[0]) return false;
456
514
  let startRange = 0;
@@ -551,10 +609,10 @@ class PDFSecurity {
551
609
  }
552
610
  infoStr += `${key}: ${info[key].valueOf()}\n`;
553
611
  }
554
- return wordArrayToBuffer(CryptoJS.MD5(infoStr));
612
+ return Buffer.from(md5Hash(infoStr));
555
613
  }
556
614
  static generateRandomWordArray(bytes) {
557
- return CryptoJS.lib.WordArray.random(bytes);
615
+ return randomBytes(bytes);
558
616
  }
559
617
  static create(document) {
560
618
  let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
@@ -648,8 +706,8 @@ class PDFSecurity {
648
706
  encDict.StrF = 'StdCF';
649
707
  }
650
708
  encDict.R = r;
651
- encDict.O = wordArrayToBuffer(ownerPasswordEntry);
652
- encDict.U = wordArrayToBuffer(userPasswordEntry);
709
+ encDict.O = Buffer.from(ownerPasswordEntry);
710
+ encDict.U = Buffer.from(userPasswordEntry);
653
711
  encDict.P = permissions;
654
712
  }
655
713
  _setupEncryptionV5(encDict, options) {
@@ -659,10 +717,10 @@ class PDFSecurity {
659
717
  const processedOwnerPassword = options.ownerPassword ? processPasswordR5(options.ownerPassword) : processedUserPassword;
660
718
  this.encryptionKey = getEncryptionKeyR5(PDFSecurity.generateRandomWordArray);
661
719
  const userPasswordEntry = getUserPasswordR5(processedUserPassword, PDFSecurity.generateRandomWordArray);
662
- const userKeySalt = CryptoJS.lib.WordArray.create(userPasswordEntry.words.slice(10, 12), 8);
720
+ const userKeySalt = userPasswordEntry.slice(40, 48);
663
721
  const userEncryptionKeyEntry = getUserEncryptionKeyR5(processedUserPassword, userKeySalt, this.encryptionKey);
664
722
  const ownerPasswordEntry = getOwnerPasswordR5(processedOwnerPassword, userPasswordEntry, PDFSecurity.generateRandomWordArray);
665
- const ownerKeySalt = CryptoJS.lib.WordArray.create(ownerPasswordEntry.words.slice(10, 12), 8);
723
+ const ownerKeySalt = ownerPasswordEntry.slice(40, 48);
666
724
  const ownerEncryptionKeyEntry = getOwnerEncryptionKeyR5(processedOwnerPassword, ownerKeySalt, userPasswordEntry, this.encryptionKey);
667
725
  const permsEntry = getEncryptedPermissionsR5(permissions, this.encryptionKey, PDFSecurity.generateRandomWordArray);
668
726
  encDict.V = 5;
@@ -677,36 +735,37 @@ class PDFSecurity {
677
735
  encDict.StmF = 'StdCF';
678
736
  encDict.StrF = 'StdCF';
679
737
  encDict.R = 5;
680
- encDict.O = wordArrayToBuffer(ownerPasswordEntry);
681
- encDict.OE = wordArrayToBuffer(ownerEncryptionKeyEntry);
682
- encDict.U = wordArrayToBuffer(userPasswordEntry);
683
- encDict.UE = wordArrayToBuffer(userEncryptionKeyEntry);
738
+ encDict.O = Buffer.from(ownerPasswordEntry);
739
+ encDict.OE = Buffer.from(ownerEncryptionKeyEntry);
740
+ encDict.U = Buffer.from(userPasswordEntry);
741
+ encDict.UE = Buffer.from(userEncryptionKeyEntry);
684
742
  encDict.P = permissions;
685
- encDict.Perms = wordArrayToBuffer(permsEntry);
743
+ encDict.Perms = Buffer.from(permsEntry);
686
744
  }
687
745
  getEncryptFn(obj, gen) {
688
746
  let digest;
689
747
  if (this.version < 5) {
690
- digest = this.encryptionKey.clone().concat(CryptoJS.lib.WordArray.create([(obj & 0xff) << 24 | (obj & 0xff00) << 8 | obj >> 8 & 0xff00 | gen & 0xff, (gen & 0xff00) << 16], 5));
748
+ const suffix = new Uint8Array([obj & 0xff, obj >> 8 & 0xff, obj >> 16 & 0xff, gen & 0xff, gen >> 8 & 0xff]);
749
+ digest = concatBytes(this.encryptionKey, suffix);
691
750
  }
692
751
  if (this.version === 1 || this.version === 2) {
693
- let key = CryptoJS.MD5(digest);
694
- key.sigBytes = Math.min(16, this.keyBits / 8 + 5);
695
- return buffer => wordArrayToBuffer(CryptoJS.RC4.encrypt(CryptoJS.lib.WordArray.create(buffer), key).ciphertext);
752
+ let key = md5Hash(digest);
753
+ const keyLen = Math.min(16, this.keyBits / 8 + 5);
754
+ key = key.slice(0, keyLen);
755
+ return buffer => Buffer.from(rc4(new Uint8Array(buffer), key));
696
756
  }
697
757
  let key;
698
758
  if (this.version === 4) {
699
- key = CryptoJS.MD5(digest.concat(CryptoJS.lib.WordArray.create([0x73416c54], 4)));
759
+ const saltMarker = new Uint8Array([0x73, 0x41, 0x6c, 0x54]);
760
+ key = md5Hash(concatBytes(digest, saltMarker));
700
761
  } else {
701
762
  key = this.encryptionKey;
702
763
  }
703
764
  const iv = PDFSecurity.generateRandomWordArray(16);
704
- const options = {
705
- mode: CryptoJS.mode.CBC,
706
- padding: CryptoJS.pad.Pkcs7,
707
- iv
765
+ return buffer => {
766
+ const encrypted = aesCbcEncrypt(new Uint8Array(buffer), key, iv, true);
767
+ return Buffer.from(concatBytes(iv, encrypted));
708
768
  };
709
- return buffer => wordArrayToBuffer(iv.clone().concat(CryptoJS.AES.encrypt(CryptoJS.lib.WordArray.create(buffer), key, options).ciphertext));
710
769
  }
711
770
  end() {
712
771
  this.dictionary.end();
@@ -759,90 +818,98 @@ function getPermissionsR3() {
759
818
  return permissions;
760
819
  }
761
820
  function getUserPasswordR2(encryptionKey) {
762
- return CryptoJS.RC4.encrypt(processPasswordR2R3R4(), encryptionKey).ciphertext;
821
+ return rc4(processPasswordR2R3R4(), encryptionKey);
763
822
  }
764
823
  function getUserPasswordR3R4(documentId, encryptionKey) {
765
- const key = encryptionKey.clone();
766
- let cipher = CryptoJS.MD5(processPasswordR2R3R4().concat(CryptoJS.lib.WordArray.create(documentId)));
824
+ const key = encryptionKey.slice();
825
+ let cipher = md5Hash(concatBytes(processPasswordR2R3R4(), new Uint8Array(documentId)));
767
826
  for (let i = 0; i < 20; i++) {
768
- const xorRound = Math.ceil(key.sigBytes / 4);
769
- for (let j = 0; j < xorRound; j++) {
770
- key.words[j] = encryptionKey.words[j] ^ (i | i << 8 | i << 16 | i << 24);
827
+ const xorKey = new Uint8Array(key.length);
828
+ for (let j = 0; j < key.length; j++) {
829
+ xorKey[j] = encryptionKey[j] ^ i;
771
830
  }
772
- cipher = CryptoJS.RC4.encrypt(cipher, key).ciphertext;
831
+ cipher = rc4(cipher, xorKey);
773
832
  }
774
- return cipher.concat(CryptoJS.lib.WordArray.create(null, 16));
833
+ const result = new Uint8Array(32);
834
+ result.set(cipher);
835
+ return result;
775
836
  }
776
837
  function getOwnerPasswordR2R3R4(r, keyBits, paddedUserPassword, paddedOwnerPassword) {
777
838
  let digest = paddedOwnerPassword;
778
839
  let round = r >= 3 ? 51 : 1;
779
840
  for (let i = 0; i < round; i++) {
780
- digest = CryptoJS.MD5(digest);
841
+ digest = md5Hash(digest);
781
842
  }
782
- const key = digest.clone();
783
- key.sigBytes = keyBits / 8;
843
+ const keyLen = keyBits / 8;
844
+ let key = digest.slice(0, keyLen);
784
845
  let cipher = paddedUserPassword;
785
846
  round = r >= 3 ? 20 : 1;
786
847
  for (let i = 0; i < round; i++) {
787
- const xorRound = Math.ceil(key.sigBytes / 4);
788
- for (let j = 0; j < xorRound; j++) {
789
- key.words[j] = digest.words[j] ^ (i | i << 8 | i << 16 | i << 24);
848
+ const xorKey = new Uint8Array(keyLen);
849
+ for (let j = 0; j < keyLen; j++) {
850
+ xorKey[j] = key[j] ^ i;
790
851
  }
791
- cipher = CryptoJS.RC4.encrypt(cipher, key).ciphertext;
852
+ cipher = rc4(cipher, xorKey);
792
853
  }
793
854
  return cipher;
794
855
  }
795
856
  function getEncryptionKeyR2R3R4(r, keyBits, documentId, paddedUserPassword, ownerPasswordEntry, permissions) {
796
- let key = paddedUserPassword.clone().concat(ownerPasswordEntry).concat(CryptoJS.lib.WordArray.create([lsbFirstWord(permissions)], 4)).concat(CryptoJS.lib.WordArray.create(documentId));
857
+ const permBytes = new Uint8Array([permissions & 0xff, permissions >> 8 & 0xff, permissions >> 16 & 0xff, permissions >> 24 & 0xff]);
858
+ let key = concatBytes(paddedUserPassword, ownerPasswordEntry, permBytes, new Uint8Array(documentId));
797
859
  const round = r >= 3 ? 51 : 1;
860
+ const keyLen = keyBits / 8;
798
861
  for (let i = 0; i < round; i++) {
799
- key = CryptoJS.MD5(key);
800
- key.sigBytes = keyBits / 8;
862
+ key = md5Hash(key);
863
+ key = key.slice(0, keyLen);
801
864
  }
802
865
  return key;
803
866
  }
804
867
  function getUserPasswordR5(processedUserPassword, generateRandomWordArray) {
805
868
  const validationSalt = generateRandomWordArray(8);
806
869
  const keySalt = generateRandomWordArray(8);
807
- return CryptoJS.SHA256(processedUserPassword.clone().concat(validationSalt)).concat(validationSalt).concat(keySalt);
870
+ const hash = sha256Hash(concatBytes(processedUserPassword, validationSalt));
871
+ return concatBytes(hash, validationSalt, keySalt);
808
872
  }
809
873
  function getUserEncryptionKeyR5(processedUserPassword, userKeySalt, encryptionKey) {
810
- const key = CryptoJS.SHA256(processedUserPassword.clone().concat(userKeySalt));
811
- const options = {
812
- mode: CryptoJS.mode.CBC,
813
- padding: CryptoJS.pad.NoPadding,
814
- iv: CryptoJS.lib.WordArray.create(null, 16)
815
- };
816
- return CryptoJS.AES.encrypt(encryptionKey, key, options).ciphertext;
874
+ const key = sha256Hash(concatBytes(processedUserPassword, userKeySalt));
875
+ const iv = new Uint8Array(16);
876
+ return aesCbcEncrypt(encryptionKey, key, iv, false);
817
877
  }
818
878
  function getOwnerPasswordR5(processedOwnerPassword, userPasswordEntry, generateRandomWordArray) {
819
879
  const validationSalt = generateRandomWordArray(8);
820
880
  const keySalt = generateRandomWordArray(8);
821
- return CryptoJS.SHA256(processedOwnerPassword.clone().concat(validationSalt).concat(userPasswordEntry)).concat(validationSalt).concat(keySalt);
881
+ const hash = sha256Hash(concatBytes(processedOwnerPassword, validationSalt, userPasswordEntry));
882
+ return concatBytes(hash, validationSalt, keySalt);
822
883
  }
823
884
  function getOwnerEncryptionKeyR5(processedOwnerPassword, ownerKeySalt, userPasswordEntry, encryptionKey) {
824
- const key = CryptoJS.SHA256(processedOwnerPassword.clone().concat(ownerKeySalt).concat(userPasswordEntry));
825
- const options = {
826
- mode: CryptoJS.mode.CBC,
827
- padding: CryptoJS.pad.NoPadding,
828
- iv: CryptoJS.lib.WordArray.create(null, 16)
829
- };
830
- return CryptoJS.AES.encrypt(encryptionKey, key, options).ciphertext;
885
+ const key = sha256Hash(concatBytes(processedOwnerPassword, ownerKeySalt, userPasswordEntry));
886
+ const iv = new Uint8Array(16);
887
+ return aesCbcEncrypt(encryptionKey, key, iv, false);
831
888
  }
832
889
  function getEncryptionKeyR5(generateRandomWordArray) {
833
890
  return generateRandomWordArray(32);
834
891
  }
835
892
  function getEncryptedPermissionsR5(permissions, encryptionKey, generateRandomWordArray) {
836
- const cipher = CryptoJS.lib.WordArray.create([lsbFirstWord(permissions), 0xffffffff, 0x54616462], 12).concat(generateRandomWordArray(4));
837
- const options = {
838
- mode: CryptoJS.mode.ECB,
839
- padding: CryptoJS.pad.NoPadding
840
- };
841
- return CryptoJS.AES.encrypt(cipher, encryptionKey, options).ciphertext;
893
+ const data = new Uint8Array(16);
894
+ data[0] = permissions & 0xff;
895
+ data[1] = permissions >> 8 & 0xff;
896
+ data[2] = permissions >> 16 & 0xff;
897
+ data[3] = permissions >> 24 & 0xff;
898
+ data[4] = 0xff;
899
+ data[5] = 0xff;
900
+ data[6] = 0xff;
901
+ data[7] = 0xff;
902
+ data[8] = 0x54;
903
+ data[9] = 0x61;
904
+ data[10] = 0x64;
905
+ data[11] = 0x62;
906
+ const randomPart = generateRandomWordArray(4);
907
+ data.set(randomPart, 12);
908
+ return aesEcbEncrypt(data, encryptionKey);
842
909
  }
843
910
  function processPasswordR2R3R4() {
844
911
  let password = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
845
- const out = Buffer.alloc(32);
912
+ const out = new Uint8Array(32);
846
913
  const length = password.length;
847
914
  let index = 0;
848
915
  while (index < length && index < 32) {
@@ -857,27 +924,17 @@ function processPasswordR2R3R4() {
857
924
  out[index] = PASSWORD_PADDING[index - length];
858
925
  index++;
859
926
  }
860
- return CryptoJS.lib.WordArray.create(out);
927
+ return out;
861
928
  }
862
929
  function processPasswordR5() {
863
930
  let password = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
864
931
  password = unescape(encodeURIComponent(saslprep(password)));
865
932
  const length = Math.min(127, password.length);
866
- const out = Buffer.alloc(length);
933
+ const out = new Uint8Array(length);
867
934
  for (let i = 0; i < length; i++) {
868
935
  out[i] = password.charCodeAt(i);
869
936
  }
870
- return CryptoJS.lib.WordArray.create(out);
871
- }
872
- function lsbFirstWord(data) {
873
- return (data & 0xff) << 24 | (data & 0xff00) << 8 | data >> 8 & 0xff00 | data >> 24 & 0xff;
874
- }
875
- function wordArrayToBuffer(wordArray) {
876
- const byteArray = [];
877
- for (let i = 0; i < wordArray.sigBytes; i++) {
878
- byteArray.push(wordArray.words[Math.floor(i / 4)] >> 8 * (3 - i % 4) & 0xff);
879
- }
880
- return Buffer.from(byteArray);
937
+ return out;
881
938
  }
882
939
  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];
883
940
 
@@ -897,14 +954,18 @@ class PDFGradient$1 {
897
954
  }
898
955
  color = this.doc._normalizeColor(color);
899
956
  if (this.stops.length === 0) {
900
- if (color.length === 3) {
901
- this._colorSpace = 'DeviceRGB';
902
- } else if (color.length === 4) {
903
- this._colorSpace = 'DeviceCMYK';
904
- } else if (color.length === 1) {
905
- this._colorSpace = 'DeviceGray';
957
+ if (this._activeColorProfile) {
958
+ this._colorSpace = this._activeColorProfile.ref;
906
959
  } else {
907
- throw new Error('Unknown color space');
960
+ if (color.length === 3) {
961
+ this._colorSpace = 'DeviceRGB';
962
+ } else if (color.length === 4) {
963
+ this._colorSpace = 'DeviceCMYK';
964
+ } else if (color.length === 1) {
965
+ this._colorSpace = 'DeviceGray';
966
+ } else {
967
+ throw new Error('Unknown color space');
968
+ }
908
969
  }
909
970
  } else if (this._colorSpace === 'DeviceRGB' && color.length !== 3 || this._colorSpace === 'DeviceCMYK' && color.length !== 4 || this._colorSpace === 'DeviceGray' && color.length !== 1) {
910
971
  throw new Error('All gradient stops must use the same color space');
@@ -1175,6 +1236,7 @@ var ColorMixin = {
1175
1236
  this._opacityCount = 0;
1176
1237
  this._patternCount = 0;
1177
1238
  this._gradCount = 0;
1239
+ this._activeColorProfile = null;
1178
1240
  },
1179
1241
  _normalizeColor(color) {
1180
1242
  if (typeof color === 'string') {
@@ -1234,6 +1296,13 @@ var ColorMixin = {
1234
1296
  if (color instanceof SpotColor) {
1235
1297
  return color.id;
1236
1298
  }
1299
+ if (this._activeColorProfile) {
1300
+ const profile = this._activeColorProfile;
1301
+ if (profile.channels !== color.length) {
1302
+ throw Error("Profile channels don't match color channels");
1303
+ }
1304
+ return profile.label;
1305
+ }
1237
1306
  return color.length === 4 ? 'DeviceCMYK' : 'DeviceRGB';
1238
1307
  },
1239
1308
  fillColor(color, opacity) {
@@ -1263,6 +1332,18 @@ var ColorMixin = {
1263
1332
  this._doOpacity(null, opacity);
1264
1333
  return this;
1265
1334
  },
1335
+ beginColorSpace(label) {
1336
+ const profile = this._colorProfiles[label];
1337
+ if (!profile) {
1338
+ throw Error('Invalid color space label, the profile should be set first');
1339
+ }
1340
+ this._activeColorProfile = profile;
1341
+ return this;
1342
+ },
1343
+ endColorSpace() {
1344
+ this._activeColorProfile = null;
1345
+ return this;
1346
+ },
1266
1347
  _doOpacity(fillOpacity, strokeOpacity) {
1267
1348
  let dictionary, name;
1268
1349
  if (fillOpacity == null && strokeOpacity == null) {
@@ -1485,86 +1566,176 @@ const parameters = {
1485
1566
  Z: 0,
1486
1567
  z: 0
1487
1568
  };
1569
+ const isCommand = function (c) {
1570
+ return c in parameters;
1571
+ };
1572
+ const isWsp = function (c) {
1573
+ const codePoint = c.codePointAt(0);
1574
+ return codePoint === 0x20 || codePoint === 0x9 || codePoint === 0xd || codePoint === 0xa;
1575
+ };
1576
+ const isDigit = function (c) {
1577
+ const codePoint = c.codePointAt(0);
1578
+ if (codePoint == null) {
1579
+ return false;
1580
+ }
1581
+ return 48 <= codePoint && codePoint <= 57;
1582
+ };
1583
+ const readNumber = function (string, cursor) {
1584
+ let i = cursor;
1585
+ let value = '';
1586
+ let state = 'none';
1587
+ for (; i < string.length; i += 1) {
1588
+ const c = string[i];
1589
+ if (c === '+' || c === '-') {
1590
+ if (state === 'none') {
1591
+ state = 'sign';
1592
+ value += c;
1593
+ continue;
1594
+ }
1595
+ if (state === 'e') {
1596
+ state = 'exponent_sign';
1597
+ value += c;
1598
+ continue;
1599
+ }
1600
+ }
1601
+ if (isDigit(c)) {
1602
+ if (state === 'none' || state === 'sign' || state === 'whole') {
1603
+ state = 'whole';
1604
+ value += c;
1605
+ continue;
1606
+ }
1607
+ if (state === 'decimal_point' || state === 'decimal') {
1608
+ state = 'decimal';
1609
+ value += c;
1610
+ continue;
1611
+ }
1612
+ if (state === 'e' || state === 'exponent_sign' || state === 'exponent') {
1613
+ state = 'exponent';
1614
+ value += c;
1615
+ continue;
1616
+ }
1617
+ }
1618
+ if (c === '.') {
1619
+ if (state === 'none' || state === 'sign' || state === 'whole') {
1620
+ state = 'decimal_point';
1621
+ value += c;
1622
+ continue;
1623
+ }
1624
+ }
1625
+ if (c === 'E' || c === 'e') {
1626
+ if (state === 'whole' || state === 'decimal_point' || state === 'decimal') {
1627
+ state = 'e';
1628
+ value += c;
1629
+ continue;
1630
+ }
1631
+ }
1632
+ break;
1633
+ }
1634
+ const number = Number.parseFloat(value);
1635
+ if (Number.isNaN(number)) {
1636
+ return [cursor, null];
1637
+ }
1638
+ return [i - 1, number];
1639
+ };
1488
1640
  const parse = function (path) {
1489
- let cmd;
1490
- const ret = [];
1641
+ const pathData = [];
1642
+ let command = null;
1491
1643
  let args = [];
1492
- let curArg = '';
1493
- let foundDecimal = false;
1494
- let params = 0;
1495
- for (let c of path) {
1496
- if (parameters[c] != null) {
1497
- params = parameters[c];
1498
- if (cmd) {
1499
- if (curArg.length > 0) {
1500
- args[args.length] = +curArg;
1644
+ let argsCount = 0;
1645
+ let canHaveComma = false;
1646
+ let hadComma = false;
1647
+ for (let i = 0; i < path.length; i += 1) {
1648
+ const c = path.charAt(i);
1649
+ if (isWsp(c)) {
1650
+ continue;
1651
+ }
1652
+ if (canHaveComma && c === ',') {
1653
+ if (hadComma) {
1654
+ break;
1655
+ }
1656
+ hadComma = true;
1657
+ continue;
1658
+ }
1659
+ if (isCommand(c)) {
1660
+ if (hadComma) {
1661
+ return pathData;
1662
+ }
1663
+ if (command == null) {
1664
+ if (c !== 'M' && c !== 'm') {
1665
+ return pathData;
1666
+ }
1667
+ } else {
1668
+ if (args.length !== 0) {
1669
+ return pathData;
1501
1670
  }
1502
- ret[ret.length] = {
1503
- cmd,
1504
- args
1505
- };
1506
- args = [];
1507
- curArg = '';
1508
- foundDecimal = false;
1509
- }
1510
- cmd = c;
1511
- } else if ([' ', ','].includes(c) || c === '-' && curArg.length > 0 && curArg[curArg.length - 1] !== 'e' || c === '.' && foundDecimal) {
1512
- if (curArg.length === 0) {
1513
- continue;
1514
1671
  }
1515
- if (args.length === params) {
1516
- ret[ret.length] = {
1517
- cmd,
1672
+ command = c;
1673
+ args = [];
1674
+ argsCount = parameters[command];
1675
+ canHaveComma = false;
1676
+ if (argsCount === 0) {
1677
+ pathData.push({
1678
+ command,
1518
1679
  args
1519
- };
1520
- args = [+curArg];
1521
- if (cmd === 'M') {
1522
- cmd = 'L';
1680
+ });
1681
+ }
1682
+ continue;
1683
+ }
1684
+ if (command == null) {
1685
+ return pathData;
1686
+ }
1687
+ let newCursor = i;
1688
+ let number = null;
1689
+ if (command === 'A' || command === 'a') {
1690
+ const position = args.length;
1691
+ if (position === 0 || position === 1) {
1692
+ if (c !== '+' && c !== '-') {
1693
+ [newCursor, number] = readNumber(path, i);
1694
+ }
1695
+ }
1696
+ if (position === 2 || position === 5 || position === 6) {
1697
+ [newCursor, number] = readNumber(path, i);
1698
+ }
1699
+ if (position === 3 || position === 4) {
1700
+ if (c === '0') {
1701
+ number = 0;
1523
1702
  }
1524
- if (cmd === 'm') {
1525
- cmd = 'l';
1703
+ if (c === '1') {
1704
+ number = 1;
1526
1705
  }
1527
- } else {
1528
- args[args.length] = +curArg;
1529
1706
  }
1530
- foundDecimal = c === '.';
1531
- curArg = ['-', '.'].includes(c) ? c : '';
1532
1707
  } else {
1533
- curArg += c;
1534
- if (c === '.') {
1535
- foundDecimal = true;
1536
- }
1537
- }
1538
- }
1539
- if (curArg.length > 0) {
1540
- if (args.length === params) {
1541
- ret[ret.length] = {
1542
- cmd,
1708
+ [newCursor, number] = readNumber(path, i);
1709
+ }
1710
+ if (number == null) {
1711
+ return pathData;
1712
+ }
1713
+ args.push(number);
1714
+ canHaveComma = true;
1715
+ hadComma = false;
1716
+ i = newCursor;
1717
+ if (args.length === argsCount) {
1718
+ pathData.push({
1719
+ command,
1543
1720
  args
1544
- };
1545
- args = [+curArg];
1546
- if (cmd === 'M') {
1547
- cmd = 'L';
1721
+ });
1722
+ if (command === 'M') {
1723
+ command = 'L';
1548
1724
  }
1549
- if (cmd === 'm') {
1550
- cmd = 'l';
1725
+ if (command === 'm') {
1726
+ command = 'l';
1551
1727
  }
1552
- } else {
1553
- args[args.length] = +curArg;
1728
+ args = [];
1554
1729
  }
1555
1730
  }
1556
- ret[ret.length] = {
1557
- cmd,
1558
- args
1559
- };
1560
- return ret;
1731
+ return pathData;
1561
1732
  };
1562
1733
  const apply = function (commands, doc) {
1563
1734
  cx = cy = px = py = sx = sy = 0;
1564
1735
  for (let i = 0; i < commands.length; i++) {
1565
1736
  const c = commands[i];
1566
- if (typeof runners[c.cmd] === 'function') {
1567
- runners[c.cmd](doc, c.args);
1737
+ if (typeof runners[c.command] === 'function') {
1738
+ runners[c.command](doc, c.args);
1568
1739
  }
1569
1740
  }
1570
1741
  };
@@ -2603,7 +2774,7 @@ begincmap
2603
2774
  1 begincodespacerange
2604
2775
  <0000><ffff>
2605
2776
  endcodespacerange
2606
- 1 beginbfrange
2777
+ ${ranges.length} beginbfrange
2607
2778
  ${ranges.join('\n')}
2608
2779
  endbfrange
2609
2780
  endcmap
@@ -2788,6 +2959,7 @@ class LineWrapper extends EventEmitter {
2788
2959
  this.document = document;
2789
2960
  this.horizontalScaling = options.horizontalScaling || 100;
2790
2961
  this.indent = (options.indent || 0) * this.horizontalScaling / 100;
2962
+ this.indentAllLines = options.indentAllLines || false;
2791
2963
  this.characterSpacing = (options.characterSpacing || 0) * this.horizontalScaling / 100;
2792
2964
  this.wordSpacing = (options.wordSpacing === 0) * this.horizontalScaling / 100;
2793
2965
  this.columns = options.columns || 1;
@@ -3029,7 +3201,13 @@ class LineWrapper extends EventEmitter {
3029
3201
  this.column = 1;
3030
3202
  this.startY = this.document.page.margins.top;
3031
3203
  this.maxY = this.document.page.maxY();
3032
- this.document.x = this.startX;
3204
+ if (this.indentAllLines) {
3205
+ const indent = this.continuedX || this.indent;
3206
+ this.document.x += indent;
3207
+ this.lineWidth -= indent;
3208
+ } else {
3209
+ this.document.x = this.startX;
3210
+ }
3033
3211
  if (this.document._fillColor) {
3034
3212
  this.document.fillColor(...this.document._fillColor);
3035
3213
  }
@@ -3439,7 +3617,11 @@ var TextMixin = {
3439
3617
  }
3440
3618
  const renderedWidth = options.textWidth + wordSpacing * (options.wordCount - 1) + characterSpacing * (text.length - 1);
3441
3619
  if (options.link != null) {
3442
- this.link(x, y, renderedWidth, this.currentLineHeight(), options.link);
3620
+ const linkOptions = {};
3621
+ if (this._currentStructureElement && this._currentStructureElement.dictionary.data.S === 'Link') {
3622
+ linkOptions.structParent = this._currentStructureElement;
3623
+ }
3624
+ this.link(x, y, renderedWidth, this.currentLineHeight(), options.link, linkOptions);
3443
3625
  }
3444
3626
  if (options.goTo != null) {
3445
3627
  this.goTo(x, y, renderedWidth, this.currentLineHeight(), options.goTo);
@@ -3568,6 +3750,47 @@ var TextMixin = {
3568
3750
  }
3569
3751
  };
3570
3752
 
3753
+ const parseExifOrientation = data => {
3754
+ if (!data || data.length < 20) return null;
3755
+ let pos = 2;
3756
+ while (pos < data.length - 4) {
3757
+ while (pos < data.length && data[pos] !== 0xff) pos++;
3758
+ if (pos >= data.length - 4) return null;
3759
+ const marker = data.readUInt16BE(pos);
3760
+ pos += 2;
3761
+ if (marker === 0xffda) return null;
3762
+ if (marker >= 0xffd0 && marker <= 0xffd9 || marker === 0xff01) continue;
3763
+ if (pos + 2 > data.length) return null;
3764
+ const segmentLength = data.readUInt16BE(pos);
3765
+ if (marker === 0xffe1 && pos + 8 <= data.length) {
3766
+ const exifHeader = data.subarray(pos + 2, pos + 8).toString('binary');
3767
+ if (exifHeader === 'Exif\x00\x00') {
3768
+ const tiffStart = pos + 8;
3769
+ if (tiffStart + 8 > data.length) return null;
3770
+ const byteOrder = data.subarray(tiffStart, tiffStart + 2).toString('ascii');
3771
+ const isLittleEndian = byteOrder === 'II';
3772
+ if (!isLittleEndian && byteOrder !== 'MM') return null;
3773
+ const read16 = isLittleEndian ? o => data.readUInt16LE(o) : o => data.readUInt16BE(o);
3774
+ const read32 = isLittleEndian ? o => data.readUInt32LE(o) : o => data.readUInt32BE(o);
3775
+ if (read16(tiffStart + 2) !== 42) return null;
3776
+ const ifdPos = tiffStart + read32(tiffStart + 4);
3777
+ if (ifdPos + 2 > data.length) return null;
3778
+ const entryCount = read16(ifdPos);
3779
+ for (let i = 0; i < entryCount; i++) {
3780
+ const entryPos = ifdPos + 2 + i * 12;
3781
+ if (entryPos + 12 > data.length) return null;
3782
+ if (read16(entryPos) === 0x0112) {
3783
+ const value = read16(entryPos + 8);
3784
+ return value >= 1 && value <= 8 ? value : null;
3785
+ }
3786
+ }
3787
+ return null;
3788
+ }
3789
+ }
3790
+ pos += segmentLength;
3791
+ }
3792
+ return null;
3793
+ };
3571
3794
  const MARKERS = [0xffc0, 0xffc1, 0xffc2, 0xffc3, 0xffc5, 0xffc6, 0xffc7, 0xffc8, 0xffc9, 0xffca, 0xffcb, 0xffcc, 0xffcd, 0xffce, 0xffcf];
3572
3795
  const COLOR_SPACE_MAP = {
3573
3796
  1: 'DeviceGray',
@@ -3582,9 +3805,11 @@ class JPEG {
3582
3805
  if (this.data.readUInt16BE(0) !== 0xffd8) {
3583
3806
  throw 'SOI not found in JPEG';
3584
3807
  }
3585
- this.orientation = exif.fromBuffer(this.data).Orientation || 1;
3808
+ this.orientation = parseExifOrientation(this.data) || 1;
3586
3809
  let pos = 2;
3587
3810
  while (pos < this.data.length) {
3811
+ while (pos < this.data.length && this.data[pos] !== 0xff) pos++;
3812
+ if (pos >= this.data.length) break;
3588
3813
  marker = this.data.readUInt16BE(pos);
3589
3814
  pos += 2;
3590
3815
  if (MARKERS.includes(marker)) {
@@ -3736,12 +3961,16 @@ class PNGImage {
3736
3961
  }
3737
3962
  loadIndexedAlphaChannel() {
3738
3963
  const transparency = this.image.transparency.indexed;
3964
+ const isInterlaced = this.image.interlaceMethod === 1;
3739
3965
  return this.image.decodePixels(pixels => {
3740
3966
  const alphaChannel = Buffer.alloc(this.width * this.height);
3741
3967
  let i = 0;
3742
3968
  for (let j = 0, end = pixels.length; j < end; j++) {
3743
3969
  alphaChannel[i++] = transparency[pixels[j]];
3744
3970
  }
3971
+ if (isInterlaced) {
3972
+ this.imgData = zlib.deflateSync(Buffer.from(pixels));
3973
+ }
3745
3974
  this.alphaChannel = zlib.deflateSync(alphaChannel);
3746
3975
  return this.finalize();
3747
3976
  });
@@ -3754,16 +3983,108 @@ class PNGImage {
3754
3983
  }
3755
3984
  }
3756
3985
 
3986
+ class BitmapImage {
3987
+ constructor(data, label, properties) {
3988
+ this.label = label;
3989
+ this.data = data;
3990
+ this.width = properties.width;
3991
+ this.height = properties.height;
3992
+ this.channels = properties.channels;
3993
+ this.colorSpace = properties.colorSpace;
3994
+ this.hasAlphaChannel = properties.hasAlphaChannel;
3995
+ this.obj = null;
3996
+ }
3997
+ embed(document) {
3998
+ if (this.obj) {
3999
+ return;
4000
+ }
4001
+ this.document = document;
4002
+ if (!(this.data instanceof Uint8Array && this.data.length === this.channels * this.width * this.height)) {
4003
+ throw Error("Invalid bitmap, data doesn't match the given properties.");
4004
+ }
4005
+ if (this.colorSpace) {
4006
+ const profile = this.document._colorProfiles[this.colorSpace];
4007
+ if (!profile) {
4008
+ throw Error("PDFDocument doesn't support bitmap color space.");
4009
+ }
4010
+ const channels = this.hasAlphaChannel ? this.channels - 1 : this.channels;
4011
+ if (profile.channels !== channels) {
4012
+ throw Error("Color profile doesn't support image channels");
4013
+ }
4014
+ this.colorSpace = profile.ref;
4015
+ } else {
4016
+ if (!this.hasAlphaChannel) {
4017
+ this.colorSpace = this.channels === 4 ? 'DeviceCMYK' : 'DeviceRGB';
4018
+ } else {
4019
+ this.colorSpace = this.channels === 5 ? 'DeviceCMYK' : 'DeviceRGB';
4020
+ }
4021
+ }
4022
+ let pixelData = this.data;
4023
+ let alphaData = undefined;
4024
+ if (this.hasAlphaChannel) {
4025
+ const pixelChannels = this.channels - 1;
4026
+ pixelData = new Uint8Array(pixelChannels * this.width * this.height);
4027
+ alphaData = new Uint8Array(this.width * this.height);
4028
+ if (this.channels === 4) {
4029
+ for (let src = 0, dst = 0, a = 0; a < this.data.length; src += 4, dst += 3, a++) {
4030
+ pixelData[dst] = this.data[src];
4031
+ pixelData[dst + 1] = this.data[src + 1];
4032
+ pixelData[dst + 2] = this.data[src + 2];
4033
+ alphaData[a] = this.data[src + 3];
4034
+ }
4035
+ } else if (this.channels === 5) {
4036
+ for (let src = 0, dst = 0, a = 0; a < this.data.length; src += 5, dst += 4, a++) {
4037
+ pixelData[dst] = this.data[src];
4038
+ pixelData[dst + 1] = this.data[src + 1];
4039
+ pixelData[dst + 2] = this.data[src + 2];
4040
+ pixelData[dst + 3] = this.data[src + 3];
4041
+ alphaData[a] = this.data[src + 4];
4042
+ }
4043
+ } else {
4044
+ for (let src = 0, dst = 0, a = 0; a < this.data.length; src += this.channels, dst += pixelChannels, a++) {
4045
+ for (let j = 0; j < pixelChannels; j++) {
4046
+ pixelData[dst + j] = this.data[src + j];
4047
+ }
4048
+ alphaData[a] = this.data[src + pixelChannels];
4049
+ }
4050
+ }
4051
+ }
4052
+ this.obj = this.document.ref({
4053
+ Type: 'XObject',
4054
+ Subtype: 'Image',
4055
+ BitsPerComponent: 8,
4056
+ Width: this.width,
4057
+ Height: this.height,
4058
+ ColorSpace: this.colorSpace
4059
+ });
4060
+ if (alphaData) {
4061
+ const sMask = this.document.ref({
4062
+ Type: 'XObject',
4063
+ Subtype: 'Image',
4064
+ Height: this.height,
4065
+ Width: this.width,
4066
+ BitsPerComponent: 8,
4067
+ ColorSpace: 'DeviceGray',
4068
+ Decode: [0, 1]
4069
+ });
4070
+ sMask.end(alphaData);
4071
+ this.obj.data['SMask'] = sMask;
4072
+ }
4073
+ this.obj.end(pixelData);
4074
+ this.data = null;
4075
+ }
4076
+ }
4077
+
3757
4078
  class PDFImage {
3758
- static open(src, label) {
4079
+ static open(src, label, properties) {
3759
4080
  let data;
3760
- if (Buffer.isBuffer(src)) {
4081
+ if (src instanceof Uint8Array || Buffer.isBuffer(src)) {
3761
4082
  data = src;
3762
4083
  } else if (src instanceof ArrayBuffer) {
3763
4084
  data = Buffer.from(new Uint8Array(src));
3764
4085
  } else {
3765
4086
  const split = src.split(',');
3766
- if (split[0].startsWith('data:') && split[0].endsWith(';base64,')) {
4087
+ if (split[0].startsWith('data:') && split[0].endsWith(';base64')) {
3767
4088
  if (split.length === 1) {
3768
4089
  throw Error('Empty base64');
3769
4090
  }
@@ -3775,6 +4096,9 @@ class PDFImage {
3775
4096
  }
3776
4097
  }
3777
4098
  }
4099
+ if (properties?.isBitmap) {
4100
+ return new BitmapImage(data, label, properties);
4101
+ }
3778
4102
  if (data[0] === 0xff && data[1] === 0xd8) {
3779
4103
  return new JPEG(data, label);
3780
4104
  } else if (data[0] === 0x89 && data.toString('ascii', 1, 4) === 'PNG') {
@@ -3959,13 +4283,13 @@ var ImagesMixin = {
3959
4283
  this.restore();
3960
4284
  return this;
3961
4285
  },
3962
- openImage(src) {
4286
+ openImage(src, properties) {
3963
4287
  let image;
3964
4288
  if (typeof src === 'string') {
3965
4289
  image = this._imageRegistry[src];
3966
4290
  }
3967
4291
  if (!image) {
3968
- image = PDFImage.open(src, `I${++this._imageCount}`);
4292
+ image = PDFImage.open(src, `I${++this._imageCount}`, properties);
3969
4293
  if (typeof src === 'string') {
3970
4294
  this._imageRegistry[src] = image;
3971
4295
  }
@@ -3974,6 +4298,12 @@ var ImagesMixin = {
3974
4298
  }
3975
4299
  };
3976
4300
 
4301
+ class PDFAnnotationReference {
4302
+ constructor(annotationRef) {
4303
+ this.annotationRef = annotationRef;
4304
+ }
4305
+ }
4306
+
3977
4307
  var AnnotationsMixin = {
3978
4308
  annotate(x, y, w, h, options) {
3979
4309
  options.Type = 'Annot';
@@ -3991,12 +4321,18 @@ var AnnotationsMixin = {
3991
4321
  if (typeof options.Dest === 'string') {
3992
4322
  options.Dest = new String(options.Dest);
3993
4323
  }
4324
+ const structParent = options.structParent;
4325
+ delete options.structParent;
3994
4326
  for (let key in options) {
3995
4327
  const val = options[key];
3996
4328
  options[key[0].toUpperCase() + key.slice(1)] = val;
3997
4329
  }
3998
4330
  const ref = this.ref(options);
3999
4331
  this.page.annotations.push(ref);
4332
+ if (structParent && typeof structParent.add === 'function') {
4333
+ const annotRef = new PDFAnnotationReference(ref);
4334
+ structParent.add(annotRef);
4335
+ }
4000
4336
  ref.end();
4001
4337
  return this;
4002
4338
  },
@@ -4043,6 +4379,9 @@ var AnnotationsMixin = {
4043
4379
  });
4044
4380
  options.A.end();
4045
4381
  }
4382
+ if (options.structParent && !options.Contents) {
4383
+ options.Contents = new String('');
4384
+ }
4046
4385
  return this.annotate(x, y, w, h, options);
4047
4386
  },
4048
4387
  _markup(x, y, w, h) {
@@ -4124,16 +4463,27 @@ var AnnotationsMixin = {
4124
4463
  }
4125
4464
  };
4126
4465
 
4466
+ const DEFAULT_OPTIONS = {
4467
+ top: 0,
4468
+ left: 0,
4469
+ zoom: 0,
4470
+ fit: true,
4471
+ pageNumber: null,
4472
+ expanded: false
4473
+ };
4127
4474
  class PDFOutline {
4128
4475
  constructor(document, parent, title, dest) {
4129
- let options = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {
4130
- expanded: false
4131
- };
4476
+ let options = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : DEFAULT_OPTIONS;
4132
4477
  this.document = document;
4133
4478
  this.options = options;
4134
4479
  this.outlineData = {};
4135
4480
  if (dest !== null) {
4136
- this.outlineData['Dest'] = [dest.dictionary, 'Fit'];
4481
+ const destWidth = dest.data.MediaBox[2];
4482
+ const destHeight = dest.data.MediaBox[3];
4483
+ const top = destHeight - (options.top || 0);
4484
+ const left = destWidth - (options.left || 0);
4485
+ const zoom = options.zoom || 0;
4486
+ this.outlineData['Dest'] = options.fit ? [dest, 'Fit'] : [dest, 'XYZ', left, top, zoom];
4137
4487
  }
4138
4488
  if (parent !== null) {
4139
4489
  this.outlineData['Parent'] = parent;
@@ -4145,10 +4495,10 @@ class PDFOutline {
4145
4495
  this.children = [];
4146
4496
  }
4147
4497
  addItem(title) {
4148
- let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
4149
- expanded: false
4150
- };
4151
- const result = new PDFOutline(this.document, this.dictionary, title, this.document.page, options);
4498
+ let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : DEFAULT_OPTIONS;
4499
+ const pages = this.document._root.data.Pages.data.Kids;
4500
+ const dest = options.pageNumber != null ? pages[options.pageNumber] : this.document.page.dictionary;
4501
+ const result = new PDFOutline(this.document, this.dictionary, title, dest, options);
4152
4502
  this.children.push(result);
4153
4503
  return result;
4154
4504
  }
@@ -4184,7 +4534,7 @@ var OutlineMixin = {
4184
4534
  this.outline.endOutline();
4185
4535
  if (this.outline.children.length > 0) {
4186
4536
  this._root.data.Outlines = this.outline.dictionary;
4187
- return this._root.data.PageMode = 'UseOutlines';
4537
+ return this._root.data.PageMode = this._root.data.PageMode || 'UseOutlines';
4188
4538
  }
4189
4539
  }
4190
4540
  };
@@ -4257,6 +4607,9 @@ class PDFStructureElement {
4257
4607
  if (child instanceof PDFStructureContent) {
4258
4608
  this._addContentToParentTree(child);
4259
4609
  }
4610
+ if (child instanceof PDFAnnotationReference) {
4611
+ this._addAnnotationToParentTree(child.annotationRef);
4612
+ }
4260
4613
  if (typeof child === 'function' && this._attached) {
4261
4614
  child = this._contentForClosure(child);
4262
4615
  }
@@ -4273,6 +4626,12 @@ class PDFStructureElement {
4273
4626
  pageStructParents[mcid] = this.dictionary;
4274
4627
  });
4275
4628
  }
4629
+ _addAnnotationToParentTree(annotRef) {
4630
+ const parentTreeKey = this.document.createStructParentTreeNextKey();
4631
+ annotRef.data.StructParent = parentTreeKey;
4632
+ const parentTree = this.document.getStructParentTree();
4633
+ parentTree.add(parentTreeKey, this.dictionary);
4634
+ }
4276
4635
  setParent(parentRef) {
4277
4636
  if (this.dictionary.data.P) {
4278
4637
  throw new Error(`Structure element added to more than one parent`);
@@ -4304,11 +4663,17 @@ class PDFStructureElement {
4304
4663
  this._flush();
4305
4664
  }
4306
4665
  _isValidChild(child) {
4307
- return child instanceof PDFStructureElement || child instanceof PDFStructureContent || typeof child === 'function';
4666
+ return child instanceof PDFStructureElement || child instanceof PDFStructureContent || child instanceof PDFAnnotationReference || typeof child === 'function';
4308
4667
  }
4309
4668
  _contentForClosure(closure) {
4310
4669
  const content = this.document.markStructureContent(this.dictionary.data.S);
4670
+ const prevStructElement = this.document._currentStructureElement;
4671
+ this.document._currentStructureElement = this;
4672
+ const wasEnded = this._ended;
4673
+ this._ended = false;
4311
4674
  closure();
4675
+ this._ended = wasEnded;
4676
+ this.document._currentStructureElement = prevStructElement;
4312
4677
  this.document.endMarkedContent();
4313
4678
  this._addContentToParentTree(content);
4314
4679
  return content;
@@ -4362,6 +4727,15 @@ class PDFStructureElement {
4362
4727
  }
4363
4728
  });
4364
4729
  }
4730
+ if (child instanceof PDFAnnotationReference) {
4731
+ const pageRef = this.document.page.dictionary;
4732
+ const objr = {
4733
+ Type: 'OBJR',
4734
+ Obj: child.annotationRef,
4735
+ Pg: pageRef
4736
+ };
4737
+ this.dictionary.data.K.push(objr);
4738
+ }
4365
4739
  }
4366
4740
  }
4367
4741
 
@@ -4457,6 +4831,13 @@ var MarkingsMixin = {
4457
4831
  endMarkedContent() {
4458
4832
  this.page.markings.pop();
4459
4833
  this.addContent('EMC');
4834
+ if (this._textOptions) {
4835
+ delete this._textOptions.link;
4836
+ delete this._textOptions.goTo;
4837
+ delete this._textOptions.destination;
4838
+ delete this._textOptions.underline;
4839
+ delete this._textOptions.strike;
4840
+ }
4460
4841
  return this;
4461
4842
  },
4462
4843
  struct(type) {
@@ -4915,7 +5296,7 @@ var AttachmentsMixin = {
4915
5296
  if (options.type) {
4916
5297
  refBody.Subtype = options.type.replace('/', '#2F');
4917
5298
  }
4918
- const checksum = CryptoJS.MD5(CryptoJS.lib.WordArray.create(new Uint8Array(data)));
5299
+ const checksum = md5Hex(new Uint8Array(data));
4919
5300
  refBody.Params.CheckSum = new String(checksum);
4920
5301
  refBody.Params.Size = data.byteLength;
4921
5302
  let ref;
@@ -4975,6 +5356,9 @@ var PDFA = {
4975
5356
  this._addColorOutputIntent();
4976
5357
  },
4977
5358
  _addColorOutputIntent() {
5359
+ if (this._root.data.OutputIntents && this._root.data.OutputIntents.length !== 0) {
5360
+ return;
5361
+ }
4978
5362
  const iccProfile = fs.readFileSync(`${__dirname}/data/sRGB_IEC61966_2_1.icc`);
4979
5363
  const colorProfileRef = this.ref({
4980
5364
  Length: iccProfile.length,
@@ -5567,7 +5951,7 @@ function renderRow(row, rowIndex) {
5567
5951
  function renderCell(cell, rowStruct) {
5568
5952
  const cellRenderer = () => {
5569
5953
  if (cell.backgroundColor != null) {
5570
- this.document.save().rect(cell.x, cell.y, cell.width, cell.height).fill(cell.backgroundColor).restore();
5954
+ this.document.save().fillColor(cell.backgroundColor).rect(cell.x, cell.y, cell.width, cell.height).fill().restore();
5571
5955
  }
5572
5956
  renderBorder.call(this, cell.border, cell.borderColor, cell.x, cell.y, cell.width, cell.height);
5573
5957
  if (cell.debug) {
@@ -5631,20 +6015,20 @@ function renderBorder(border, borderColor, x, y, width, height, mask) {
5631
6015
  const doc = this.document;
5632
6016
  if ([border.right, border.bottom, border.left].every(val => val === border.top)) {
5633
6017
  if (border.top > 0) {
5634
- doc.save().lineWidth(border.top).rect(x, y, width, height).stroke(borderColor.top).restore();
6018
+ doc.save().lineWidth(border.top).strokeColor(borderColor.top).rect(x, y, width, height).stroke().restore();
5635
6019
  }
5636
6020
  } else {
5637
6021
  if (border.top > 0) {
5638
- doc.save().lineWidth(border.top).moveTo(x, y).lineTo(x + width, y).stroke(borderColor.top).restore();
6022
+ doc.save().lineWidth(border.top).moveTo(x, y).strokeColor(borderColor.top).lineTo(x + width, y).stroke().restore();
5639
6023
  }
5640
6024
  if (border.right > 0) {
5641
- doc.save().lineWidth(border.right).moveTo(x + width, y).lineTo(x + width, y + height).stroke(borderColor.right).restore();
6025
+ doc.save().lineWidth(border.right).moveTo(x + width, y).strokeColor(borderColor.right).lineTo(x + width, y + height).stroke().restore();
5642
6026
  }
5643
6027
  if (border.bottom > 0) {
5644
- doc.save().lineWidth(border.bottom).moveTo(x + width, y + height).lineTo(x, y + height).stroke(borderColor.bottom).restore();
6028
+ doc.save().lineWidth(border.bottom).moveTo(x + width, y + height).strokeColor(borderColor.bottom).lineTo(x, y + height).stroke().restore();
5645
6029
  }
5646
6030
  if (border.left > 0) {
5647
- doc.save().lineWidth(border.left).moveTo(x, y + height).lineTo(x, y).stroke(borderColor.left).restore();
6031
+ doc.save().lineWidth(border.left).moveTo(x, y + height).strokeColor(borderColor.left).lineTo(x, y).stroke().restore();
5648
6032
  }
5649
6033
  }
5650
6034
  }
@@ -5810,6 +6194,73 @@ var MetadataMixin = {
5810
6194
  }
5811
6195
  };
5812
6196
 
6197
+ class ICCProfile {
6198
+ constructor(label, data, channels, alternate) {
6199
+ this.label = label;
6200
+ this.data = data;
6201
+ this.channels = channels;
6202
+ this.alternate = alternate;
6203
+ this.ref = null;
6204
+ this.streamRef = null;
6205
+ }
6206
+ embed(document) {
6207
+ if (this.ref) {
6208
+ return;
6209
+ }
6210
+ this.document = document;
6211
+ this.streamRef = this.document.ref({
6212
+ Alternate: this.alternate,
6213
+ N: this.channels,
6214
+ Length: this.data.length
6215
+ });
6216
+ this.streamRef.end(this.data);
6217
+ this.ref = this.document.ref([`ICCBased ${this.streamRef}`]);
6218
+ this.ref.end();
6219
+ this.data = null;
6220
+ }
6221
+ }
6222
+
6223
+ var ColorSpaceMixin = {
6224
+ initColorSpace() {
6225
+ this._colorProfiles = {};
6226
+ },
6227
+ iccProfile(label, data, channels, alternate) {
6228
+ const profile = new ICCProfile(label, data, channels, alternate);
6229
+ profile.embed(this);
6230
+ this._colorProfiles[label] = profile;
6231
+ return this;
6232
+ },
6233
+ _writeSpaceToResources(resources) {
6234
+ resources.data.ColorSpace = resources.data.ColorSpace || {};
6235
+ Object.entries(this._colorProfiles).forEach(_ref => {
6236
+ let [k, v] = _ref;
6237
+ resources.data.ColorSpace[k] = v.ref;
6238
+ });
6239
+ }
6240
+ };
6241
+
6242
+ var OutputIntent = {
6243
+ initOutputIntent() {
6244
+ this._root.data.OutputIntents = this._root.data.OutputIntents || [];
6245
+ },
6246
+ outputIntent(type, s, info, outputConditionIdentifier, destOutputProfileLabel) {
6247
+ const profile = this._colorProfiles[destOutputProfileLabel];
6248
+ if (profile) {
6249
+ throw Error('Invalid color profile label, the profile should be set first');
6250
+ }
6251
+ const intentRef = this.ref({
6252
+ Type: type,
6253
+ S: s,
6254
+ Info: info,
6255
+ OutputConditionIdentifier: outputConditionIdentifier,
6256
+ DestOutputProfile: profile.ref
6257
+ });
6258
+ intentRef.end();
6259
+ this._root.data.OutputIntents.push(intentRef);
6260
+ return this;
6261
+ }
6262
+ };
6263
+
5813
6264
  class PDFDocument extends stream.Readable {
5814
6265
  constructor() {
5815
6266
  let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
@@ -5856,8 +6307,13 @@ class PDFDocument extends stream.Readable {
5856
6307
  if (this.options.lang) {
5857
6308
  this._root.data.Lang = new String(this.options.lang);
5858
6309
  }
6310
+ if (this.options.pageLayout) {
6311
+ const layout = this.options.pageLayout;
6312
+ this._root.data.PageLayout = layout.charAt(0).toUpperCase() + layout.slice(1);
6313
+ }
5859
6314
  this.page = null;
5860
6315
  this.initMetadata();
6316
+ this.initColorSpace();
5861
6317
  this.initColor();
5862
6318
  this.initVector();
5863
6319
  this.initFonts(options.font);
@@ -5867,6 +6323,7 @@ class PDFDocument extends stream.Readable {
5867
6323
  this.initMarkings(options);
5868
6324
  this.initTables();
5869
6325
  this.initSubset(options);
6326
+ this.initOutputIntent();
5870
6327
  this.info = {
5871
6328
  Producer: 'PDFKit',
5872
6329
  Creator: 'PDFKit',
@@ -6068,6 +6525,7 @@ const mixin = methods => {
6068
6525
  Object.assign(PDFDocument.prototype, methods);
6069
6526
  };
6070
6527
  mixin(MetadataMixin);
6528
+ mixin(ColorSpaceMixin);
6071
6529
  mixin(ColorMixin);
6072
6530
  mixin(VectorMixin);
6073
6531
  mixin(FontsMixin);
@@ -6080,6 +6538,7 @@ mixin(AcroFormMixin);
6080
6538
  mixin(AttachmentsMixin);
6081
6539
  mixin(SubsetMixin);
6082
6540
  mixin(TableMixin);
6541
+ mixin(OutputIntent);
6083
6542
  PDFDocument.LineWrapper = LineWrapper;
6084
6543
 
6085
6544
  export { PDFDocument as default };