@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.js CHANGED
@@ -2,12 +2,14 @@
2
2
 
3
3
  var stream = require('stream');
4
4
  var zlib = require('zlib');
5
- var CryptoJS = require('crypto-js');
5
+ var utils = require('@noble/hashes/utils');
6
+ var md5 = require('js-md5');
7
+ var sha256 = require('@noble/hashes/sha256');
8
+ var aes = require('@noble/ciphers/aes');
6
9
  var fs = require('fs');
7
10
  var fontkit = require('fontkit');
8
11
  var events = require('events');
9
12
  var LineBreaker = require('linebreak');
10
- var exif = require('jpeg-exif');
11
13
  var PNG = require('png-js');
12
14
 
13
15
  class PDFAbstractReference {
@@ -359,6 +361,7 @@ class PDFPage {
359
361
  this._options = options;
360
362
  this.size = options.size || 'letter';
361
363
  this.layout = options.layout || 'portrait';
364
+ this.userUnit = options.userUnit || 1.0;
362
365
  const dimensions = Array.isArray(this.size) ? this.size : SIZES[this.size.toUpperCase()];
363
366
  this.width = dimensions[this.layout === 'portrait' ? 0 : 1];
364
367
  this.height = dimensions[this.layout === 'portrait' ? 1 : 0];
@@ -374,7 +377,8 @@ class PDFPage {
374
377
  Parent: this.document._root.data.Pages,
375
378
  MediaBox: [0, 0, this.width, this.height],
376
379
  Contents: this.content,
377
- Resources: this.resources
380
+ Resources: this.resources,
381
+ UserUnit: this.userUnit
378
382
  });
379
383
  this.markings = [];
380
384
  }
@@ -430,6 +434,7 @@ class PDFPage {
430
434
  for (let color of Object.values(this.document.spotColors)) {
431
435
  this.resources.data.ColorSpace[color.id] = color;
432
436
  }
437
+ this.document._writeSpaceToResources(this.resources);
433
438
  this.resources.end();
434
439
  return this.content.end();
435
440
  }
@@ -447,6 +452,58 @@ class PDFNameTree extends PDFTree {
447
452
  }
448
453
  }
449
454
 
455
+ function md5Hash(data) {
456
+ return new Uint8Array(md5.arrayBuffer(data));
457
+ }
458
+ function md5Hex(data) {
459
+ return md5(data);
460
+ }
461
+
462
+ function sha256Hash(data) {
463
+ return sha256.sha256(data);
464
+ }
465
+
466
+ function aesCbcEncrypt(data, key, iv, padding = true) {
467
+ return aes.cbc(key, iv, {
468
+ disablePadding: !padding
469
+ }).encrypt(data);
470
+ }
471
+ function aesEcbEncrypt(data, key) {
472
+ return aes.ecb(key, {
473
+ disablePadding: true
474
+ }).encrypt(data);
475
+ }
476
+
477
+ function rc4(data, key) {
478
+ const s = new Uint8Array(256);
479
+ for (let i = 0; i < 256; i++) {
480
+ s[i] = i;
481
+ }
482
+ let j = 0;
483
+ for (let i = 0; i < 256; i++) {
484
+ j = j + s[i] + key[i % key.length] & 0xff;
485
+ [s[i], s[j]] = [s[j], s[i]];
486
+ }
487
+ const output = new Uint8Array(data.length);
488
+ for (let i = 0, j = 0, k = 0; k < data.length; k++) {
489
+ i = i + 1 & 0xff;
490
+ j = j + s[i] & 0xff;
491
+ [s[i], s[j]] = [s[j], s[i]];
492
+ output[k] = data[k] ^ s[s[i] + s[j] & 0xff];
493
+ }
494
+ return output;
495
+ }
496
+
497
+ function randomBytes(length) {
498
+ const bytes = new Uint8Array(length);
499
+ if (globalThis.crypto?.getRandomValues) {
500
+ globalThis.crypto.getRandomValues(bytes);
501
+ } else {
502
+ require('crypto').randomFillSync(bytes);
503
+ }
504
+ return bytes;
505
+ }
506
+
450
507
  function inRange(value, rangeGroup) {
451
508
  if (value < rangeGroup[0]) return false;
452
509
  let startRange = 0;
@@ -545,10 +602,10 @@ class PDFSecurity {
545
602
  }
546
603
  infoStr += `${key}: ${info[key].valueOf()}\n`;
547
604
  }
548
- return wordArrayToBuffer(CryptoJS.MD5(infoStr));
605
+ return Buffer.from(md5Hash(infoStr));
549
606
  }
550
607
  static generateRandomWordArray(bytes) {
551
- return CryptoJS.lib.WordArray.random(bytes);
608
+ return randomBytes(bytes);
552
609
  }
553
610
  static create(document, options = {}) {
554
611
  if (!options.ownerPassword && !options.userPassword) {
@@ -640,8 +697,8 @@ class PDFSecurity {
640
697
  encDict.StrF = 'StdCF';
641
698
  }
642
699
  encDict.R = r;
643
- encDict.O = wordArrayToBuffer(ownerPasswordEntry);
644
- encDict.U = wordArrayToBuffer(userPasswordEntry);
700
+ encDict.O = Buffer.from(ownerPasswordEntry);
701
+ encDict.U = Buffer.from(userPasswordEntry);
645
702
  encDict.P = permissions;
646
703
  }
647
704
  _setupEncryptionV5(encDict, options) {
@@ -651,10 +708,10 @@ class PDFSecurity {
651
708
  const processedOwnerPassword = options.ownerPassword ? processPasswordR5(options.ownerPassword) : processedUserPassword;
652
709
  this.encryptionKey = getEncryptionKeyR5(PDFSecurity.generateRandomWordArray);
653
710
  const userPasswordEntry = getUserPasswordR5(processedUserPassword, PDFSecurity.generateRandomWordArray);
654
- const userKeySalt = CryptoJS.lib.WordArray.create(userPasswordEntry.words.slice(10, 12), 8);
711
+ const userKeySalt = userPasswordEntry.slice(40, 48);
655
712
  const userEncryptionKeyEntry = getUserEncryptionKeyR5(processedUserPassword, userKeySalt, this.encryptionKey);
656
713
  const ownerPasswordEntry = getOwnerPasswordR5(processedOwnerPassword, userPasswordEntry, PDFSecurity.generateRandomWordArray);
657
- const ownerKeySalt = CryptoJS.lib.WordArray.create(ownerPasswordEntry.words.slice(10, 12), 8);
714
+ const ownerKeySalt = ownerPasswordEntry.slice(40, 48);
658
715
  const ownerEncryptionKeyEntry = getOwnerEncryptionKeyR5(processedOwnerPassword, ownerKeySalt, userPasswordEntry, this.encryptionKey);
659
716
  const permsEntry = getEncryptedPermissionsR5(permissions, this.encryptionKey, PDFSecurity.generateRandomWordArray);
660
717
  encDict.V = 5;
@@ -669,36 +726,37 @@ class PDFSecurity {
669
726
  encDict.StmF = 'StdCF';
670
727
  encDict.StrF = 'StdCF';
671
728
  encDict.R = 5;
672
- encDict.O = wordArrayToBuffer(ownerPasswordEntry);
673
- encDict.OE = wordArrayToBuffer(ownerEncryptionKeyEntry);
674
- encDict.U = wordArrayToBuffer(userPasswordEntry);
675
- encDict.UE = wordArrayToBuffer(userEncryptionKeyEntry);
729
+ encDict.O = Buffer.from(ownerPasswordEntry);
730
+ encDict.OE = Buffer.from(ownerEncryptionKeyEntry);
731
+ encDict.U = Buffer.from(userPasswordEntry);
732
+ encDict.UE = Buffer.from(userEncryptionKeyEntry);
676
733
  encDict.P = permissions;
677
- encDict.Perms = wordArrayToBuffer(permsEntry);
734
+ encDict.Perms = Buffer.from(permsEntry);
678
735
  }
679
736
  getEncryptFn(obj, gen) {
680
737
  let digest;
681
738
  if (this.version < 5) {
682
- digest = this.encryptionKey.clone().concat(CryptoJS.lib.WordArray.create([(obj & 0xff) << 24 | (obj & 0xff00) << 8 | obj >> 8 & 0xff00 | gen & 0xff, (gen & 0xff00) << 16], 5));
739
+ const suffix = new Uint8Array([obj & 0xff, obj >> 8 & 0xff, obj >> 16 & 0xff, gen & 0xff, gen >> 8 & 0xff]);
740
+ digest = utils.concatBytes(this.encryptionKey, suffix);
683
741
  }
684
742
  if (this.version === 1 || this.version === 2) {
685
- let key = CryptoJS.MD5(digest);
686
- key.sigBytes = Math.min(16, this.keyBits / 8 + 5);
687
- return buffer => wordArrayToBuffer(CryptoJS.RC4.encrypt(CryptoJS.lib.WordArray.create(buffer), key).ciphertext);
743
+ let key = md5Hash(digest);
744
+ const keyLen = Math.min(16, this.keyBits / 8 + 5);
745
+ key = key.slice(0, keyLen);
746
+ return buffer => Buffer.from(rc4(new Uint8Array(buffer), key));
688
747
  }
689
748
  let key;
690
749
  if (this.version === 4) {
691
- key = CryptoJS.MD5(digest.concat(CryptoJS.lib.WordArray.create([0x73416c54], 4)));
750
+ const saltMarker = new Uint8Array([0x73, 0x41, 0x6c, 0x54]);
751
+ key = md5Hash(utils.concatBytes(digest, saltMarker));
692
752
  } else {
693
753
  key = this.encryptionKey;
694
754
  }
695
755
  const iv = PDFSecurity.generateRandomWordArray(16);
696
- const options = {
697
- mode: CryptoJS.mode.CBC,
698
- padding: CryptoJS.pad.Pkcs7,
699
- iv
756
+ return buffer => {
757
+ const encrypted = aesCbcEncrypt(new Uint8Array(buffer), key, iv, true);
758
+ return Buffer.from(utils.concatBytes(iv, encrypted));
700
759
  };
701
- return buffer => wordArrayToBuffer(iv.clone().concat(CryptoJS.AES.encrypt(CryptoJS.lib.WordArray.create(buffer), key, options).ciphertext));
702
760
  }
703
761
  end() {
704
762
  this.dictionary.end();
@@ -749,89 +807,97 @@ function getPermissionsR3(permissionObject = {}) {
749
807
  return permissions;
750
808
  }
751
809
  function getUserPasswordR2(encryptionKey) {
752
- return CryptoJS.RC4.encrypt(processPasswordR2R3R4(), encryptionKey).ciphertext;
810
+ return rc4(processPasswordR2R3R4(), encryptionKey);
753
811
  }
754
812
  function getUserPasswordR3R4(documentId, encryptionKey) {
755
- const key = encryptionKey.clone();
756
- let cipher = CryptoJS.MD5(processPasswordR2R3R4().concat(CryptoJS.lib.WordArray.create(documentId)));
813
+ const key = encryptionKey.slice();
814
+ let cipher = md5Hash(utils.concatBytes(processPasswordR2R3R4(), new Uint8Array(documentId)));
757
815
  for (let i = 0; i < 20; i++) {
758
- const xorRound = Math.ceil(key.sigBytes / 4);
759
- for (let j = 0; j < xorRound; j++) {
760
- key.words[j] = encryptionKey.words[j] ^ (i | i << 8 | i << 16 | i << 24);
816
+ const xorKey = new Uint8Array(key.length);
817
+ for (let j = 0; j < key.length; j++) {
818
+ xorKey[j] = encryptionKey[j] ^ i;
761
819
  }
762
- cipher = CryptoJS.RC4.encrypt(cipher, key).ciphertext;
820
+ cipher = rc4(cipher, xorKey);
763
821
  }
764
- return cipher.concat(CryptoJS.lib.WordArray.create(null, 16));
822
+ const result = new Uint8Array(32);
823
+ result.set(cipher);
824
+ return result;
765
825
  }
766
826
  function getOwnerPasswordR2R3R4(r, keyBits, paddedUserPassword, paddedOwnerPassword) {
767
827
  let digest = paddedOwnerPassword;
768
828
  let round = r >= 3 ? 51 : 1;
769
829
  for (let i = 0; i < round; i++) {
770
- digest = CryptoJS.MD5(digest);
830
+ digest = md5Hash(digest);
771
831
  }
772
- const key = digest.clone();
773
- key.sigBytes = keyBits / 8;
832
+ const keyLen = keyBits / 8;
833
+ let key = digest.slice(0, keyLen);
774
834
  let cipher = paddedUserPassword;
775
835
  round = r >= 3 ? 20 : 1;
776
836
  for (let i = 0; i < round; i++) {
777
- const xorRound = Math.ceil(key.sigBytes / 4);
778
- for (let j = 0; j < xorRound; j++) {
779
- key.words[j] = digest.words[j] ^ (i | i << 8 | i << 16 | i << 24);
837
+ const xorKey = new Uint8Array(keyLen);
838
+ for (let j = 0; j < keyLen; j++) {
839
+ xorKey[j] = key[j] ^ i;
780
840
  }
781
- cipher = CryptoJS.RC4.encrypt(cipher, key).ciphertext;
841
+ cipher = rc4(cipher, xorKey);
782
842
  }
783
843
  return cipher;
784
844
  }
785
845
  function getEncryptionKeyR2R3R4(r, keyBits, documentId, paddedUserPassword, ownerPasswordEntry, permissions) {
786
- let key = paddedUserPassword.clone().concat(ownerPasswordEntry).concat(CryptoJS.lib.WordArray.create([lsbFirstWord(permissions)], 4)).concat(CryptoJS.lib.WordArray.create(documentId));
846
+ const permBytes = new Uint8Array([permissions & 0xff, permissions >> 8 & 0xff, permissions >> 16 & 0xff, permissions >> 24 & 0xff]);
847
+ let key = utils.concatBytes(paddedUserPassword, ownerPasswordEntry, permBytes, new Uint8Array(documentId));
787
848
  const round = r >= 3 ? 51 : 1;
849
+ const keyLen = keyBits / 8;
788
850
  for (let i = 0; i < round; i++) {
789
- key = CryptoJS.MD5(key);
790
- key.sigBytes = keyBits / 8;
851
+ key = md5Hash(key);
852
+ key = key.slice(0, keyLen);
791
853
  }
792
854
  return key;
793
855
  }
794
856
  function getUserPasswordR5(processedUserPassword, generateRandomWordArray) {
795
857
  const validationSalt = generateRandomWordArray(8);
796
858
  const keySalt = generateRandomWordArray(8);
797
- return CryptoJS.SHA256(processedUserPassword.clone().concat(validationSalt)).concat(validationSalt).concat(keySalt);
859
+ const hash = sha256Hash(utils.concatBytes(processedUserPassword, validationSalt));
860
+ return utils.concatBytes(hash, validationSalt, keySalt);
798
861
  }
799
862
  function getUserEncryptionKeyR5(processedUserPassword, userKeySalt, encryptionKey) {
800
- const key = CryptoJS.SHA256(processedUserPassword.clone().concat(userKeySalt));
801
- const options = {
802
- mode: CryptoJS.mode.CBC,
803
- padding: CryptoJS.pad.NoPadding,
804
- iv: CryptoJS.lib.WordArray.create(null, 16)
805
- };
806
- return CryptoJS.AES.encrypt(encryptionKey, key, options).ciphertext;
863
+ const key = sha256Hash(utils.concatBytes(processedUserPassword, userKeySalt));
864
+ const iv = new Uint8Array(16);
865
+ return aesCbcEncrypt(encryptionKey, key, iv, false);
807
866
  }
808
867
  function getOwnerPasswordR5(processedOwnerPassword, userPasswordEntry, generateRandomWordArray) {
809
868
  const validationSalt = generateRandomWordArray(8);
810
869
  const keySalt = generateRandomWordArray(8);
811
- return CryptoJS.SHA256(processedOwnerPassword.clone().concat(validationSalt).concat(userPasswordEntry)).concat(validationSalt).concat(keySalt);
870
+ const hash = sha256Hash(utils.concatBytes(processedOwnerPassword, validationSalt, userPasswordEntry));
871
+ return utils.concatBytes(hash, validationSalt, keySalt);
812
872
  }
813
873
  function getOwnerEncryptionKeyR5(processedOwnerPassword, ownerKeySalt, userPasswordEntry, encryptionKey) {
814
- const key = CryptoJS.SHA256(processedOwnerPassword.clone().concat(ownerKeySalt).concat(userPasswordEntry));
815
- const options = {
816
- mode: CryptoJS.mode.CBC,
817
- padding: CryptoJS.pad.NoPadding,
818
- iv: CryptoJS.lib.WordArray.create(null, 16)
819
- };
820
- return CryptoJS.AES.encrypt(encryptionKey, key, options).ciphertext;
874
+ const key = sha256Hash(utils.concatBytes(processedOwnerPassword, ownerKeySalt, userPasswordEntry));
875
+ const iv = new Uint8Array(16);
876
+ return aesCbcEncrypt(encryptionKey, key, iv, false);
821
877
  }
822
878
  function getEncryptionKeyR5(generateRandomWordArray) {
823
879
  return generateRandomWordArray(32);
824
880
  }
825
881
  function getEncryptedPermissionsR5(permissions, encryptionKey, generateRandomWordArray) {
826
- const cipher = CryptoJS.lib.WordArray.create([lsbFirstWord(permissions), 0xffffffff, 0x54616462], 12).concat(generateRandomWordArray(4));
827
- const options = {
828
- mode: CryptoJS.mode.ECB,
829
- padding: CryptoJS.pad.NoPadding
830
- };
831
- return CryptoJS.AES.encrypt(cipher, encryptionKey, options).ciphertext;
882
+ const data = new Uint8Array(16);
883
+ data[0] = permissions & 0xff;
884
+ data[1] = permissions >> 8 & 0xff;
885
+ data[2] = permissions >> 16 & 0xff;
886
+ data[3] = permissions >> 24 & 0xff;
887
+ data[4] = 0xff;
888
+ data[5] = 0xff;
889
+ data[6] = 0xff;
890
+ data[7] = 0xff;
891
+ data[8] = 0x54;
892
+ data[9] = 0x61;
893
+ data[10] = 0x64;
894
+ data[11] = 0x62;
895
+ const randomPart = generateRandomWordArray(4);
896
+ data.set(randomPart, 12);
897
+ return aesEcbEncrypt(data, encryptionKey);
832
898
  }
833
899
  function processPasswordR2R3R4(password = '') {
834
- const out = Buffer.alloc(32);
900
+ const out = new Uint8Array(32);
835
901
  const length = password.length;
836
902
  let index = 0;
837
903
  while (index < length && index < 32) {
@@ -846,26 +912,16 @@ function processPasswordR2R3R4(password = '') {
846
912
  out[index] = PASSWORD_PADDING[index - length];
847
913
  index++;
848
914
  }
849
- return CryptoJS.lib.WordArray.create(out);
915
+ return out;
850
916
  }
851
917
  function processPasswordR5(password = '') {
852
918
  password = unescape(encodeURIComponent(saslprep(password)));
853
919
  const length = Math.min(127, password.length);
854
- const out = Buffer.alloc(length);
920
+ const out = new Uint8Array(length);
855
921
  for (let i = 0; i < length; i++) {
856
922
  out[i] = password.charCodeAt(i);
857
923
  }
858
- return CryptoJS.lib.WordArray.create(out);
859
- }
860
- function lsbFirstWord(data) {
861
- return (data & 0xff) << 24 | (data & 0xff00) << 8 | data >> 8 & 0xff00 | data >> 24 & 0xff;
862
- }
863
- function wordArrayToBuffer(wordArray) {
864
- const byteArray = [];
865
- for (let i = 0; i < wordArray.sigBytes; i++) {
866
- byteArray.push(wordArray.words[Math.floor(i / 4)] >> 8 * (3 - i % 4) & 0xff);
867
- }
868
- return Buffer.from(byteArray);
924
+ return out;
869
925
  }
870
926
  const PASSWORD_PADDING = [0x28, 0xbf, 0x4e, 0x5e, 0x4e, 0x75, 0x8a, 0x41, 0x64, 0x00, 0x4e, 0x56, 0xff, 0xfa, 0x01, 0x08, 0x2e, 0x2e, 0x00, 0xb6, 0xd0, 0x68, 0x3e, 0x80, 0x2f, 0x0c, 0xa9, 0xfe, 0x64, 0x53, 0x69, 0x7a];
871
927
 
@@ -885,14 +941,18 @@ class PDFGradient$1 {
885
941
  }
886
942
  color = this.doc._normalizeColor(color);
887
943
  if (this.stops.length === 0) {
888
- if (color.length === 3) {
889
- this._colorSpace = 'DeviceRGB';
890
- } else if (color.length === 4) {
891
- this._colorSpace = 'DeviceCMYK';
892
- } else if (color.length === 1) {
893
- this._colorSpace = 'DeviceGray';
944
+ if (this._activeColorProfile) {
945
+ this._colorSpace = this._activeColorProfile.ref;
894
946
  } else {
895
- throw new Error('Unknown color space');
947
+ if (color.length === 3) {
948
+ this._colorSpace = 'DeviceRGB';
949
+ } else if (color.length === 4) {
950
+ this._colorSpace = 'DeviceCMYK';
951
+ } else if (color.length === 1) {
952
+ this._colorSpace = 'DeviceGray';
953
+ } else {
954
+ throw new Error('Unknown color space');
955
+ }
896
956
  }
897
957
  } else if (this._colorSpace === 'DeviceRGB' && color.length !== 3 || this._colorSpace === 'DeviceCMYK' && color.length !== 4 || this._colorSpace === 'DeviceGray' && color.length !== 1) {
898
958
  throw new Error('All gradient stops must use the same color space');
@@ -1163,6 +1223,7 @@ var ColorMixin = {
1163
1223
  this._opacityCount = 0;
1164
1224
  this._patternCount = 0;
1165
1225
  this._gradCount = 0;
1226
+ this._activeColorProfile = null;
1166
1227
  },
1167
1228
  _normalizeColor(color) {
1168
1229
  if (typeof color === 'string') {
@@ -1222,6 +1283,13 @@ var ColorMixin = {
1222
1283
  if (color instanceof SpotColor) {
1223
1284
  return color.id;
1224
1285
  }
1286
+ if (this._activeColorProfile) {
1287
+ const profile = this._activeColorProfile;
1288
+ if (profile.channels !== color.length) {
1289
+ throw Error("Profile channels don't match color channels");
1290
+ }
1291
+ return profile.label;
1292
+ }
1225
1293
  return color.length === 4 ? 'DeviceCMYK' : 'DeviceRGB';
1226
1294
  },
1227
1295
  fillColor(color, opacity) {
@@ -1251,6 +1319,18 @@ var ColorMixin = {
1251
1319
  this._doOpacity(null, opacity);
1252
1320
  return this;
1253
1321
  },
1322
+ beginColorSpace(label) {
1323
+ const profile = this._colorProfiles[label];
1324
+ if (!profile) {
1325
+ throw Error('Invalid color space label, the profile should be set first');
1326
+ }
1327
+ this._activeColorProfile = profile;
1328
+ return this;
1329
+ },
1330
+ endColorSpace() {
1331
+ this._activeColorProfile = null;
1332
+ return this;
1333
+ },
1254
1334
  _doOpacity(fillOpacity, strokeOpacity) {
1255
1335
  let dictionary, name;
1256
1336
  if (fillOpacity == null && strokeOpacity == null) {
@@ -1473,86 +1553,176 @@ const parameters = {
1473
1553
  Z: 0,
1474
1554
  z: 0
1475
1555
  };
1556
+ const isCommand = function (c) {
1557
+ return c in parameters;
1558
+ };
1559
+ const isWsp = function (c) {
1560
+ const codePoint = c.codePointAt(0);
1561
+ return codePoint === 0x20 || codePoint === 0x9 || codePoint === 0xd || codePoint === 0xa;
1562
+ };
1563
+ const isDigit = function (c) {
1564
+ const codePoint = c.codePointAt(0);
1565
+ if (codePoint == null) {
1566
+ return false;
1567
+ }
1568
+ return 48 <= codePoint && codePoint <= 57;
1569
+ };
1570
+ const readNumber = function (string, cursor) {
1571
+ let i = cursor;
1572
+ let value = '';
1573
+ let state = 'none';
1574
+ for (; i < string.length; i += 1) {
1575
+ const c = string[i];
1576
+ if (c === '+' || c === '-') {
1577
+ if (state === 'none') {
1578
+ state = 'sign';
1579
+ value += c;
1580
+ continue;
1581
+ }
1582
+ if (state === 'e') {
1583
+ state = 'exponent_sign';
1584
+ value += c;
1585
+ continue;
1586
+ }
1587
+ }
1588
+ if (isDigit(c)) {
1589
+ if (state === 'none' || state === 'sign' || state === 'whole') {
1590
+ state = 'whole';
1591
+ value += c;
1592
+ continue;
1593
+ }
1594
+ if (state === 'decimal_point' || state === 'decimal') {
1595
+ state = 'decimal';
1596
+ value += c;
1597
+ continue;
1598
+ }
1599
+ if (state === 'e' || state === 'exponent_sign' || state === 'exponent') {
1600
+ state = 'exponent';
1601
+ value += c;
1602
+ continue;
1603
+ }
1604
+ }
1605
+ if (c === '.') {
1606
+ if (state === 'none' || state === 'sign' || state === 'whole') {
1607
+ state = 'decimal_point';
1608
+ value += c;
1609
+ continue;
1610
+ }
1611
+ }
1612
+ if (c === 'E' || c === 'e') {
1613
+ if (state === 'whole' || state === 'decimal_point' || state === 'decimal') {
1614
+ state = 'e';
1615
+ value += c;
1616
+ continue;
1617
+ }
1618
+ }
1619
+ break;
1620
+ }
1621
+ const number = Number.parseFloat(value);
1622
+ if (Number.isNaN(number)) {
1623
+ return [cursor, null];
1624
+ }
1625
+ return [i - 1, number];
1626
+ };
1476
1627
  const parse = function (path) {
1477
- let cmd;
1478
- const ret = [];
1628
+ const pathData = [];
1629
+ let command = null;
1479
1630
  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;
1631
+ let argsCount = 0;
1632
+ let canHaveComma = false;
1633
+ let hadComma = false;
1634
+ for (let i = 0; i < path.length; i += 1) {
1635
+ const c = path.charAt(i);
1636
+ if (isWsp(c)) {
1637
+ continue;
1638
+ }
1639
+ if (canHaveComma && c === ',') {
1640
+ if (hadComma) {
1641
+ break;
1642
+ }
1643
+ hadComma = true;
1644
+ continue;
1645
+ }
1646
+ if (isCommand(c)) {
1647
+ if (hadComma) {
1648
+ return pathData;
1649
+ }
1650
+ if (command == null) {
1651
+ if (c !== 'M' && c !== 'm') {
1652
+ return pathData;
1653
+ }
1654
+ } else {
1655
+ if (args.length !== 0) {
1656
+ return pathData;
1489
1657
  }
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
1658
  }
1503
- if (args.length === params) {
1504
- ret[ret.length] = {
1505
- cmd,
1659
+ command = c;
1660
+ args = [];
1661
+ argsCount = parameters[command];
1662
+ canHaveComma = false;
1663
+ if (argsCount === 0) {
1664
+ pathData.push({
1665
+ command,
1506
1666
  args
1507
- };
1508
- args = [+curArg];
1509
- if (cmd === 'M') {
1510
- cmd = 'L';
1667
+ });
1668
+ }
1669
+ continue;
1670
+ }
1671
+ if (command == null) {
1672
+ return pathData;
1673
+ }
1674
+ let newCursor = i;
1675
+ let number = null;
1676
+ if (command === 'A' || command === 'a') {
1677
+ const position = args.length;
1678
+ if (position === 0 || position === 1) {
1679
+ if (c !== '+' && c !== '-') {
1680
+ [newCursor, number] = readNumber(path, i);
1511
1681
  }
1512
- if (cmd === 'm') {
1513
- cmd = 'l';
1682
+ }
1683
+ if (position === 2 || position === 5 || position === 6) {
1684
+ [newCursor, number] = readNumber(path, i);
1685
+ }
1686
+ if (position === 3 || position === 4) {
1687
+ if (c === '0') {
1688
+ number = 0;
1689
+ }
1690
+ if (c === '1') {
1691
+ number = 1;
1514
1692
  }
1515
- } else {
1516
- args[args.length] = +curArg;
1517
1693
  }
1518
- foundDecimal = c === '.';
1519
- curArg = ['-', '.'].includes(c) ? c : '';
1520
1694
  } 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,
1695
+ [newCursor, number] = readNumber(path, i);
1696
+ }
1697
+ if (number == null) {
1698
+ return pathData;
1699
+ }
1700
+ args.push(number);
1701
+ canHaveComma = true;
1702
+ hadComma = false;
1703
+ i = newCursor;
1704
+ if (args.length === argsCount) {
1705
+ pathData.push({
1706
+ command,
1531
1707
  args
1532
- };
1533
- args = [+curArg];
1534
- if (cmd === 'M') {
1535
- cmd = 'L';
1708
+ });
1709
+ if (command === 'M') {
1710
+ command = 'L';
1536
1711
  }
1537
- if (cmd === 'm') {
1538
- cmd = 'l';
1712
+ if (command === 'm') {
1713
+ command = 'l';
1539
1714
  }
1540
- } else {
1541
- args[args.length] = +curArg;
1715
+ args = [];
1542
1716
  }
1543
1717
  }
1544
- ret[ret.length] = {
1545
- cmd,
1546
- args
1547
- };
1548
- return ret;
1718
+ return pathData;
1549
1719
  };
1550
1720
  const apply = function (commands, doc) {
1551
1721
  cx = cy = px = py = sx = sy = 0;
1552
1722
  for (let i = 0; i < commands.length; i++) {
1553
1723
  const c = commands[i];
1554
- if (typeof runners[c.cmd] === 'function') {
1555
- runners[c.cmd](doc, c.args);
1724
+ if (typeof runners[c.command] === 'function') {
1725
+ runners[c.command](doc, c.args);
1556
1726
  }
1557
1727
  }
1558
1728
  };
@@ -2584,7 +2754,7 @@ begincmap
2584
2754
  1 begincodespacerange
2585
2755
  <0000><ffff>
2586
2756
  endcodespacerange
2587
- 1 beginbfrange
2757
+ ${ranges.length} beginbfrange
2588
2758
  ${ranges.join('\n')}
2589
2759
  endbfrange
2590
2760
  endcmap
@@ -2763,6 +2933,7 @@ class LineWrapper extends events.EventEmitter {
2763
2933
  this.document = document;
2764
2934
  this.horizontalScaling = options.horizontalScaling || 100;
2765
2935
  this.indent = (options.indent || 0) * this.horizontalScaling / 100;
2936
+ this.indentAllLines = options.indentAllLines || false;
2766
2937
  this.characterSpacing = (options.characterSpacing || 0) * this.horizontalScaling / 100;
2767
2938
  this.wordSpacing = (options.wordSpacing === 0) * this.horizontalScaling / 100;
2768
2939
  this.columns = options.columns || 1;
@@ -3004,7 +3175,13 @@ class LineWrapper extends events.EventEmitter {
3004
3175
  this.column = 1;
3005
3176
  this.startY = this.document.page.margins.top;
3006
3177
  this.maxY = this.document.page.maxY();
3007
- this.document.x = this.startX;
3178
+ if (this.indentAllLines) {
3179
+ const indent = this.continuedX || this.indent;
3180
+ this.document.x += indent;
3181
+ this.lineWidth -= indent;
3182
+ } else {
3183
+ this.document.x = this.startX;
3184
+ }
3008
3185
  if (this.document._fillColor) {
3009
3186
  this.document.fillColor(...this.document._fillColor);
3010
3187
  }
@@ -3408,7 +3585,11 @@ var TextMixin = {
3408
3585
  }
3409
3586
  const renderedWidth = options.textWidth + wordSpacing * (options.wordCount - 1) + characterSpacing * (text.length - 1);
3410
3587
  if (options.link != null) {
3411
- this.link(x, y, renderedWidth, this.currentLineHeight(), options.link);
3588
+ const linkOptions = {};
3589
+ if (this._currentStructureElement && this._currentStructureElement.dictionary.data.S === 'Link') {
3590
+ linkOptions.structParent = this._currentStructureElement;
3591
+ }
3592
+ this.link(x, y, renderedWidth, this.currentLineHeight(), options.link, linkOptions);
3412
3593
  }
3413
3594
  if (options.goTo != null) {
3414
3595
  this.goTo(x, y, renderedWidth, this.currentLineHeight(), options.goTo);
@@ -3537,6 +3718,47 @@ var TextMixin = {
3537
3718
  }
3538
3719
  };
3539
3720
 
3721
+ const parseExifOrientation = data => {
3722
+ if (!data || data.length < 20) return null;
3723
+ let pos = 2;
3724
+ while (pos < data.length - 4) {
3725
+ while (pos < data.length && data[pos] !== 0xff) pos++;
3726
+ if (pos >= data.length - 4) return null;
3727
+ const marker = data.readUInt16BE(pos);
3728
+ pos += 2;
3729
+ if (marker === 0xffda) return null;
3730
+ if (marker >= 0xffd0 && marker <= 0xffd9 || marker === 0xff01) continue;
3731
+ if (pos + 2 > data.length) return null;
3732
+ const segmentLength = data.readUInt16BE(pos);
3733
+ if (marker === 0xffe1 && pos + 8 <= data.length) {
3734
+ const exifHeader = data.subarray(pos + 2, pos + 8).toString('binary');
3735
+ if (exifHeader === 'Exif\x00\x00') {
3736
+ const tiffStart = pos + 8;
3737
+ if (tiffStart + 8 > data.length) return null;
3738
+ const byteOrder = data.subarray(tiffStart, tiffStart + 2).toString('ascii');
3739
+ const isLittleEndian = byteOrder === 'II';
3740
+ if (!isLittleEndian && byteOrder !== 'MM') return null;
3741
+ const read16 = isLittleEndian ? o => data.readUInt16LE(o) : o => data.readUInt16BE(o);
3742
+ const read32 = isLittleEndian ? o => data.readUInt32LE(o) : o => data.readUInt32BE(o);
3743
+ if (read16(tiffStart + 2) !== 42) return null;
3744
+ const ifdPos = tiffStart + read32(tiffStart + 4);
3745
+ if (ifdPos + 2 > data.length) return null;
3746
+ const entryCount = read16(ifdPos);
3747
+ for (let i = 0; i < entryCount; i++) {
3748
+ const entryPos = ifdPos + 2 + i * 12;
3749
+ if (entryPos + 12 > data.length) return null;
3750
+ if (read16(entryPos) === 0x0112) {
3751
+ const value = read16(entryPos + 8);
3752
+ return value >= 1 && value <= 8 ? value : null;
3753
+ }
3754
+ }
3755
+ return null;
3756
+ }
3757
+ }
3758
+ pos += segmentLength;
3759
+ }
3760
+ return null;
3761
+ };
3540
3762
  const MARKERS = [0xffc0, 0xffc1, 0xffc2, 0xffc3, 0xffc5, 0xffc6, 0xffc7, 0xffc8, 0xffc9, 0xffca, 0xffcb, 0xffcc, 0xffcd, 0xffce, 0xffcf];
3541
3763
  const COLOR_SPACE_MAP = {
3542
3764
  1: 'DeviceGray',
@@ -3551,9 +3773,11 @@ class JPEG {
3551
3773
  if (this.data.readUInt16BE(0) !== 0xffd8) {
3552
3774
  throw 'SOI not found in JPEG';
3553
3775
  }
3554
- this.orientation = exif.fromBuffer(this.data).Orientation || 1;
3776
+ this.orientation = parseExifOrientation(this.data) || 1;
3555
3777
  let pos = 2;
3556
3778
  while (pos < this.data.length) {
3779
+ while (pos < this.data.length && this.data[pos] !== 0xff) pos++;
3780
+ if (pos >= this.data.length) break;
3557
3781
  marker = this.data.readUInt16BE(pos);
3558
3782
  pos += 2;
3559
3783
  if (MARKERS.includes(marker)) {
@@ -3705,12 +3929,16 @@ class PNGImage {
3705
3929
  }
3706
3930
  loadIndexedAlphaChannel() {
3707
3931
  const transparency = this.image.transparency.indexed;
3932
+ const isInterlaced = this.image.interlaceMethod === 1;
3708
3933
  return this.image.decodePixels(pixels => {
3709
3934
  const alphaChannel = Buffer.alloc(this.width * this.height);
3710
3935
  let i = 0;
3711
3936
  for (let j = 0, end = pixels.length; j < end; j++) {
3712
3937
  alphaChannel[i++] = transparency[pixels[j]];
3713
3938
  }
3939
+ if (isInterlaced) {
3940
+ this.imgData = zlib.deflateSync(Buffer.from(pixels));
3941
+ }
3714
3942
  this.alphaChannel = zlib.deflateSync(alphaChannel);
3715
3943
  return this.finalize();
3716
3944
  });
@@ -3723,16 +3951,108 @@ class PNGImage {
3723
3951
  }
3724
3952
  }
3725
3953
 
3954
+ class BitmapImage {
3955
+ constructor(data, label, properties) {
3956
+ this.label = label;
3957
+ this.data = data;
3958
+ this.width = properties.width;
3959
+ this.height = properties.height;
3960
+ this.channels = properties.channels;
3961
+ this.colorSpace = properties.colorSpace;
3962
+ this.hasAlphaChannel = properties.hasAlphaChannel;
3963
+ this.obj = null;
3964
+ }
3965
+ embed(document) {
3966
+ if (this.obj) {
3967
+ return;
3968
+ }
3969
+ this.document = document;
3970
+ if (!(this.data instanceof Uint8Array && this.data.length === this.channels * this.width * this.height)) {
3971
+ throw Error("Invalid bitmap, data doesn't match the given properties.");
3972
+ }
3973
+ if (this.colorSpace) {
3974
+ const profile = this.document._colorProfiles[this.colorSpace];
3975
+ if (!profile) {
3976
+ throw Error("PDFDocument doesn't support bitmap color space.");
3977
+ }
3978
+ const channels = this.hasAlphaChannel ? this.channels - 1 : this.channels;
3979
+ if (profile.channels !== channels) {
3980
+ throw Error("Color profile doesn't support image channels");
3981
+ }
3982
+ this.colorSpace = profile.ref;
3983
+ } else {
3984
+ if (!this.hasAlphaChannel) {
3985
+ this.colorSpace = this.channels === 4 ? 'DeviceCMYK' : 'DeviceRGB';
3986
+ } else {
3987
+ this.colorSpace = this.channels === 5 ? 'DeviceCMYK' : 'DeviceRGB';
3988
+ }
3989
+ }
3990
+ let pixelData = this.data;
3991
+ let alphaData = undefined;
3992
+ if (this.hasAlphaChannel) {
3993
+ const pixelChannels = this.channels - 1;
3994
+ pixelData = new Uint8Array(pixelChannels * this.width * this.height);
3995
+ alphaData = new Uint8Array(this.width * this.height);
3996
+ if (this.channels === 4) {
3997
+ for (let src = 0, dst = 0, a = 0; a < this.data.length; src += 4, dst += 3, a++) {
3998
+ pixelData[dst] = this.data[src];
3999
+ pixelData[dst + 1] = this.data[src + 1];
4000
+ pixelData[dst + 2] = this.data[src + 2];
4001
+ alphaData[a] = this.data[src + 3];
4002
+ }
4003
+ } else if (this.channels === 5) {
4004
+ for (let src = 0, dst = 0, a = 0; a < this.data.length; src += 5, dst += 4, a++) {
4005
+ pixelData[dst] = this.data[src];
4006
+ pixelData[dst + 1] = this.data[src + 1];
4007
+ pixelData[dst + 2] = this.data[src + 2];
4008
+ pixelData[dst + 3] = this.data[src + 3];
4009
+ alphaData[a] = this.data[src + 4];
4010
+ }
4011
+ } else {
4012
+ for (let src = 0, dst = 0, a = 0; a < this.data.length; src += this.channels, dst += pixelChannels, a++) {
4013
+ for (let j = 0; j < pixelChannels; j++) {
4014
+ pixelData[dst + j] = this.data[src + j];
4015
+ }
4016
+ alphaData[a] = this.data[src + pixelChannels];
4017
+ }
4018
+ }
4019
+ }
4020
+ this.obj = this.document.ref({
4021
+ Type: 'XObject',
4022
+ Subtype: 'Image',
4023
+ BitsPerComponent: 8,
4024
+ Width: this.width,
4025
+ Height: this.height,
4026
+ ColorSpace: this.colorSpace
4027
+ });
4028
+ if (alphaData) {
4029
+ const sMask = this.document.ref({
4030
+ Type: 'XObject',
4031
+ Subtype: 'Image',
4032
+ Height: this.height,
4033
+ Width: this.width,
4034
+ BitsPerComponent: 8,
4035
+ ColorSpace: 'DeviceGray',
4036
+ Decode: [0, 1]
4037
+ });
4038
+ sMask.end(alphaData);
4039
+ this.obj.data['SMask'] = sMask;
4040
+ }
4041
+ this.obj.end(pixelData);
4042
+ this.data = null;
4043
+ }
4044
+ }
4045
+
3726
4046
  class PDFImage {
3727
- static open(src, label) {
4047
+ static open(src, label, properties) {
3728
4048
  let data;
3729
- if (Buffer.isBuffer(src)) {
4049
+ if (src instanceof Uint8Array || Buffer.isBuffer(src)) {
3730
4050
  data = src;
3731
4051
  } else if (src instanceof ArrayBuffer) {
3732
4052
  data = Buffer.from(new Uint8Array(src));
3733
4053
  } else {
3734
4054
  const split = src.split(',');
3735
- if (split[0].startsWith('data:') && split[0].endsWith(';base64,')) {
4055
+ if (split[0].startsWith('data:') && split[0].endsWith(';base64')) {
3736
4056
  if (split.length === 1) {
3737
4057
  throw Error('Empty base64');
3738
4058
  }
@@ -3744,6 +4064,9 @@ class PDFImage {
3744
4064
  }
3745
4065
  }
3746
4066
  }
4067
+ if (properties?.isBitmap) {
4068
+ return new BitmapImage(data, label, properties);
4069
+ }
3747
4070
  if (data[0] === 0xff && data[1] === 0xd8) {
3748
4071
  return new JPEG(data, label);
3749
4072
  } else if (data[0] === 0x89 && data.toString('ascii', 1, 4) === 'PNG') {
@@ -3927,13 +4250,13 @@ var ImagesMixin = {
3927
4250
  this.restore();
3928
4251
  return this;
3929
4252
  },
3930
- openImage(src) {
4253
+ openImage(src, properties) {
3931
4254
  let image;
3932
4255
  if (typeof src === 'string') {
3933
4256
  image = this._imageRegistry[src];
3934
4257
  }
3935
4258
  if (!image) {
3936
- image = PDFImage.open(src, `I${++this._imageCount}`);
4259
+ image = PDFImage.open(src, `I${++this._imageCount}`, properties);
3937
4260
  if (typeof src === 'string') {
3938
4261
  this._imageRegistry[src] = image;
3939
4262
  }
@@ -3942,6 +4265,12 @@ var ImagesMixin = {
3942
4265
  }
3943
4266
  };
3944
4267
 
4268
+ class PDFAnnotationReference {
4269
+ constructor(annotationRef) {
4270
+ this.annotationRef = annotationRef;
4271
+ }
4272
+ }
4273
+
3945
4274
  var AnnotationsMixin = {
3946
4275
  annotate(x, y, w, h, options) {
3947
4276
  options.Type = 'Annot';
@@ -3959,12 +4288,18 @@ var AnnotationsMixin = {
3959
4288
  if (typeof options.Dest === 'string') {
3960
4289
  options.Dest = new String(options.Dest);
3961
4290
  }
4291
+ const structParent = options.structParent;
4292
+ delete options.structParent;
3962
4293
  for (let key in options) {
3963
4294
  const val = options[key];
3964
4295
  options[key[0].toUpperCase() + key.slice(1)] = val;
3965
4296
  }
3966
4297
  const ref = this.ref(options);
3967
4298
  this.page.annotations.push(ref);
4299
+ if (structParent && typeof structParent.add === 'function') {
4300
+ const annotRef = new PDFAnnotationReference(ref);
4301
+ structParent.add(annotRef);
4302
+ }
3968
4303
  ref.end();
3969
4304
  return this;
3970
4305
  },
@@ -4008,6 +4343,9 @@ var AnnotationsMixin = {
4008
4343
  });
4009
4344
  options.A.end();
4010
4345
  }
4346
+ if (options.structParent && !options.Contents) {
4347
+ options.Contents = new String('');
4348
+ }
4011
4349
  return this.annotate(x, y, w, h, options);
4012
4350
  },
4013
4351
  _markup(x, y, w, h, options = {}) {
@@ -4079,15 +4417,26 @@ var AnnotationsMixin = {
4079
4417
  }
4080
4418
  };
4081
4419
 
4420
+ const DEFAULT_OPTIONS = {
4421
+ top: 0,
4422
+ left: 0,
4423
+ zoom: 0,
4424
+ fit: true,
4425
+ pageNumber: null,
4426
+ expanded: false
4427
+ };
4082
4428
  class PDFOutline {
4083
- constructor(document, parent, title, dest, options = {
4084
- expanded: false
4085
- }) {
4429
+ constructor(document, parent, title, dest, options = DEFAULT_OPTIONS) {
4086
4430
  this.document = document;
4087
4431
  this.options = options;
4088
4432
  this.outlineData = {};
4089
4433
  if (dest !== null) {
4090
- this.outlineData['Dest'] = [dest.dictionary, 'Fit'];
4434
+ const destWidth = dest.data.MediaBox[2];
4435
+ const destHeight = dest.data.MediaBox[3];
4436
+ const top = destHeight - (options.top || 0);
4437
+ const left = destWidth - (options.left || 0);
4438
+ const zoom = options.zoom || 0;
4439
+ this.outlineData['Dest'] = options.fit ? [dest, 'Fit'] : [dest, 'XYZ', left, top, zoom];
4091
4440
  }
4092
4441
  if (parent !== null) {
4093
4442
  this.outlineData['Parent'] = parent;
@@ -4098,10 +4447,10 @@ class PDFOutline {
4098
4447
  this.dictionary = this.document.ref(this.outlineData);
4099
4448
  this.children = [];
4100
4449
  }
4101
- addItem(title, options = {
4102
- expanded: false
4103
- }) {
4104
- const result = new PDFOutline(this.document, this.dictionary, title, this.document.page, options);
4450
+ addItem(title, options = DEFAULT_OPTIONS) {
4451
+ const pages = this.document._root.data.Pages.data.Kids;
4452
+ const dest = options.pageNumber != null ? pages[options.pageNumber] : this.document.page.dictionary;
4453
+ const result = new PDFOutline(this.document, this.dictionary, title, dest, options);
4105
4454
  this.children.push(result);
4106
4455
  return result;
4107
4456
  }
@@ -4137,7 +4486,7 @@ var OutlineMixin = {
4137
4486
  this.outline.endOutline();
4138
4487
  if (this.outline.children.length > 0) {
4139
4488
  this._root.data.Outlines = this.outline.dictionary;
4140
- return this._root.data.PageMode = 'UseOutlines';
4489
+ return this._root.data.PageMode = this._root.data.PageMode || 'UseOutlines';
4141
4490
  }
4142
4491
  }
4143
4492
  };
@@ -4208,6 +4557,9 @@ class PDFStructureElement {
4208
4557
  if (child instanceof PDFStructureContent) {
4209
4558
  this._addContentToParentTree(child);
4210
4559
  }
4560
+ if (child instanceof PDFAnnotationReference) {
4561
+ this._addAnnotationToParentTree(child.annotationRef);
4562
+ }
4211
4563
  if (typeof child === 'function' && this._attached) {
4212
4564
  child = this._contentForClosure(child);
4213
4565
  }
@@ -4223,6 +4575,12 @@ class PDFStructureElement {
4223
4575
  pageStructParents[mcid] = this.dictionary;
4224
4576
  });
4225
4577
  }
4578
+ _addAnnotationToParentTree(annotRef) {
4579
+ const parentTreeKey = this.document.createStructParentTreeNextKey();
4580
+ annotRef.data.StructParent = parentTreeKey;
4581
+ const parentTree = this.document.getStructParentTree();
4582
+ parentTree.add(parentTreeKey, this.dictionary);
4583
+ }
4226
4584
  setParent(parentRef) {
4227
4585
  if (this.dictionary.data.P) {
4228
4586
  throw new Error(`Structure element added to more than one parent`);
@@ -4254,11 +4612,17 @@ class PDFStructureElement {
4254
4612
  this._flush();
4255
4613
  }
4256
4614
  _isValidChild(child) {
4257
- return child instanceof PDFStructureElement || child instanceof PDFStructureContent || typeof child === 'function';
4615
+ return child instanceof PDFStructureElement || child instanceof PDFStructureContent || child instanceof PDFAnnotationReference || typeof child === 'function';
4258
4616
  }
4259
4617
  _contentForClosure(closure) {
4260
4618
  const content = this.document.markStructureContent(this.dictionary.data.S);
4619
+ const prevStructElement = this.document._currentStructureElement;
4620
+ this.document._currentStructureElement = this;
4621
+ const wasEnded = this._ended;
4622
+ this._ended = false;
4261
4623
  closure();
4624
+ this._ended = wasEnded;
4625
+ this.document._currentStructureElement = prevStructElement;
4262
4626
  this.document.endMarkedContent();
4263
4627
  this._addContentToParentTree(content);
4264
4628
  return content;
@@ -4311,6 +4675,15 @@ class PDFStructureElement {
4311
4675
  }
4312
4676
  });
4313
4677
  }
4678
+ if (child instanceof PDFAnnotationReference) {
4679
+ const pageRef = this.document.page.dictionary;
4680
+ const objr = {
4681
+ Type: 'OBJR',
4682
+ Obj: child.annotationRef,
4683
+ Pg: pageRef
4684
+ };
4685
+ this.dictionary.data.K.push(objr);
4686
+ }
4314
4687
  }
4315
4688
  }
4316
4689
 
@@ -4404,6 +4777,13 @@ var MarkingsMixin = {
4404
4777
  endMarkedContent() {
4405
4778
  this.page.markings.pop();
4406
4779
  this.addContent('EMC');
4780
+ if (this._textOptions) {
4781
+ delete this._textOptions.link;
4782
+ delete this._textOptions.goTo;
4783
+ delete this._textOptions.destination;
4784
+ delete this._textOptions.underline;
4785
+ delete this._textOptions.strike;
4786
+ }
4407
4787
  return this;
4408
4788
  },
4409
4789
  struct(type, options = {}, children = null) {
@@ -4850,7 +5230,7 @@ var AttachmentsMixin = {
4850
5230
  if (options.type) {
4851
5231
  refBody.Subtype = options.type.replace('/', '#2F');
4852
5232
  }
4853
- const checksum = CryptoJS.MD5(CryptoJS.lib.WordArray.create(new Uint8Array(data)));
5233
+ const checksum = md5Hex(new Uint8Array(data));
4854
5234
  refBody.Params.CheckSum = new String(checksum);
4855
5235
  refBody.Params.Size = data.byteLength;
4856
5236
  let ref;
@@ -4910,6 +5290,9 @@ var PDFA = {
4910
5290
  this._addColorOutputIntent();
4911
5291
  },
4912
5292
  _addColorOutputIntent() {
5293
+ if (this._root.data.OutputIntents && this._root.data.OutputIntents.length !== 0) {
5294
+ return;
5295
+ }
4913
5296
  const iccProfile = fs.readFileSync(`${__dirname}/data/sRGB_IEC61966_2_1.icc`);
4914
5297
  const colorProfileRef = this.ref({
4915
5298
  Length: iccProfile.length,
@@ -5493,7 +5876,7 @@ function renderRow(row, rowIndex) {
5493
5876
  function renderCell(cell, rowStruct) {
5494
5877
  const cellRenderer = () => {
5495
5878
  if (cell.backgroundColor != null) {
5496
- this.document.save().rect(cell.x, cell.y, cell.width, cell.height).fill(cell.backgroundColor).restore();
5879
+ this.document.save().fillColor(cell.backgroundColor).rect(cell.x, cell.y, cell.width, cell.height).fill().restore();
5497
5880
  }
5498
5881
  renderBorder.call(this, cell.border, cell.borderColor, cell.x, cell.y, cell.width, cell.height);
5499
5882
  if (cell.debug) {
@@ -5554,20 +5937,20 @@ function renderBorder(border, borderColor, x, y, width, height, mask) {
5554
5937
  const doc = this.document;
5555
5938
  if ([border.right, border.bottom, border.left].every(val => val === border.top)) {
5556
5939
  if (border.top > 0) {
5557
- doc.save().lineWidth(border.top).rect(x, y, width, height).stroke(borderColor.top).restore();
5940
+ doc.save().lineWidth(border.top).strokeColor(borderColor.top).rect(x, y, width, height).stroke().restore();
5558
5941
  }
5559
5942
  } else {
5560
5943
  if (border.top > 0) {
5561
- doc.save().lineWidth(border.top).moveTo(x, y).lineTo(x + width, y).stroke(borderColor.top).restore();
5944
+ doc.save().lineWidth(border.top).moveTo(x, y).strokeColor(borderColor.top).lineTo(x + width, y).stroke().restore();
5562
5945
  }
5563
5946
  if (border.right > 0) {
5564
- doc.save().lineWidth(border.right).moveTo(x + width, y).lineTo(x + width, y + height).stroke(borderColor.right).restore();
5947
+ doc.save().lineWidth(border.right).moveTo(x + width, y).strokeColor(borderColor.right).lineTo(x + width, y + height).stroke().restore();
5565
5948
  }
5566
5949
  if (border.bottom > 0) {
5567
- doc.save().lineWidth(border.bottom).moveTo(x + width, y + height).lineTo(x, y + height).stroke(borderColor.bottom).restore();
5950
+ doc.save().lineWidth(border.bottom).moveTo(x + width, y + height).strokeColor(borderColor.bottom).lineTo(x, y + height).stroke().restore();
5568
5951
  }
5569
5952
  if (border.left > 0) {
5570
- doc.save().lineWidth(border.left).moveTo(x, y + height).lineTo(x, y).stroke(borderColor.left).restore();
5953
+ doc.save().lineWidth(border.left).moveTo(x, y + height).strokeColor(borderColor.left).lineTo(x, y).stroke().restore();
5571
5954
  }
5572
5955
  }
5573
5956
  }
@@ -5729,6 +6112,72 @@ var MetadataMixin = {
5729
6112
  }
5730
6113
  };
5731
6114
 
6115
+ class ICCProfile {
6116
+ constructor(label, data, channels, alternate) {
6117
+ this.label = label;
6118
+ this.data = data;
6119
+ this.channels = channels;
6120
+ this.alternate = alternate;
6121
+ this.ref = null;
6122
+ this.streamRef = null;
6123
+ }
6124
+ embed(document) {
6125
+ if (this.ref) {
6126
+ return;
6127
+ }
6128
+ this.document = document;
6129
+ this.streamRef = this.document.ref({
6130
+ Alternate: this.alternate,
6131
+ N: this.channels,
6132
+ Length: this.data.length
6133
+ });
6134
+ this.streamRef.end(this.data);
6135
+ this.ref = this.document.ref([`ICCBased ${this.streamRef}`]);
6136
+ this.ref.end();
6137
+ this.data = null;
6138
+ }
6139
+ }
6140
+
6141
+ var ColorSpaceMixin = {
6142
+ initColorSpace() {
6143
+ this._colorProfiles = {};
6144
+ },
6145
+ iccProfile(label, data, channels, alternate) {
6146
+ const profile = new ICCProfile(label, data, channels, alternate);
6147
+ profile.embed(this);
6148
+ this._colorProfiles[label] = profile;
6149
+ return this;
6150
+ },
6151
+ _writeSpaceToResources(resources) {
6152
+ resources.data.ColorSpace = resources.data.ColorSpace || {};
6153
+ Object.entries(this._colorProfiles).forEach(([k, v]) => {
6154
+ resources.data.ColorSpace[k] = v.ref;
6155
+ });
6156
+ }
6157
+ };
6158
+
6159
+ var OutputIntent = {
6160
+ initOutputIntent() {
6161
+ this._root.data.OutputIntents = this._root.data.OutputIntents || [];
6162
+ },
6163
+ outputIntent(type, s, info, outputConditionIdentifier, destOutputProfileLabel) {
6164
+ const profile = this._colorProfiles[destOutputProfileLabel];
6165
+ if (profile) {
6166
+ throw Error('Invalid color profile label, the profile should be set first');
6167
+ }
6168
+ const intentRef = this.ref({
6169
+ Type: type,
6170
+ S: s,
6171
+ Info: info,
6172
+ OutputConditionIdentifier: outputConditionIdentifier,
6173
+ DestOutputProfile: profile.ref
6174
+ });
6175
+ intentRef.end();
6176
+ this._root.data.OutputIntents.push(intentRef);
6177
+ return this;
6178
+ }
6179
+ };
6180
+
5732
6181
  class PDFDocument extends stream.Readable {
5733
6182
  constructor(options = {}) {
5734
6183
  super(options);
@@ -5774,8 +6223,13 @@ class PDFDocument extends stream.Readable {
5774
6223
  if (this.options.lang) {
5775
6224
  this._root.data.Lang = new String(this.options.lang);
5776
6225
  }
6226
+ if (this.options.pageLayout) {
6227
+ const layout = this.options.pageLayout;
6228
+ this._root.data.PageLayout = layout.charAt(0).toUpperCase() + layout.slice(1);
6229
+ }
5777
6230
  this.page = null;
5778
6231
  this.initMetadata();
6232
+ this.initColorSpace();
5779
6233
  this.initColor();
5780
6234
  this.initVector();
5781
6235
  this.initFonts(options.font);
@@ -5785,6 +6239,7 @@ class PDFDocument extends stream.Readable {
5785
6239
  this.initMarkings(options);
5786
6240
  this.initTables();
5787
6241
  this.initSubset(options);
6242
+ this.initOutputIntent();
5788
6243
  this.info = {
5789
6244
  Producer: 'PDFKit',
5790
6245
  Creator: 'PDFKit',
@@ -5983,6 +6438,7 @@ const mixin = methods => {
5983
6438
  Object.assign(PDFDocument.prototype, methods);
5984
6439
  };
5985
6440
  mixin(MetadataMixin);
6441
+ mixin(ColorSpaceMixin);
5986
6442
  mixin(ColorMixin);
5987
6443
  mixin(VectorMixin);
5988
6444
  mixin(FontsMixin);
@@ -5995,6 +6451,7 @@ mixin(AcroFormMixin);
5995
6451
  mixin(AttachmentsMixin);
5996
6452
  mixin(SubsetMixin);
5997
6453
  mixin(TableMixin);
6454
+ mixin(OutputIntent);
5998
6455
  PDFDocument.LineWrapper = LineWrapper;
5999
6456
 
6000
6457
  module.exports = PDFDocument;