pdfkit 0.17.2 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/js/pdfkit.js CHANGED
@@ -2,12 +2,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
  }
@@ -447,6 +451,58 @@ class PDFNameTree extends PDFTree {
447
451
  }
448
452
  }
449
453
 
454
+ function md5Hash(data) {
455
+ return new Uint8Array(md5.arrayBuffer(data));
456
+ }
457
+ function md5Hex(data) {
458
+ return md5(data);
459
+ }
460
+
461
+ function sha256Hash(data) {
462
+ return sha256.sha256(data);
463
+ }
464
+
465
+ function aesCbcEncrypt(data, key, iv, padding = true) {
466
+ return aes.cbc(key, iv, {
467
+ disablePadding: !padding
468
+ }).encrypt(data);
469
+ }
470
+ function aesEcbEncrypt(data, key) {
471
+ return aes.ecb(key, {
472
+ disablePadding: true
473
+ }).encrypt(data);
474
+ }
475
+
476
+ function rc4(data, key) {
477
+ const s = new Uint8Array(256);
478
+ for (let i = 0; i < 256; i++) {
479
+ s[i] = i;
480
+ }
481
+ let j = 0;
482
+ for (let i = 0; i < 256; i++) {
483
+ j = j + s[i] + key[i % key.length] & 0xff;
484
+ [s[i], s[j]] = [s[j], s[i]];
485
+ }
486
+ const output = new Uint8Array(data.length);
487
+ for (let i = 0, j = 0, k = 0; k < data.length; k++) {
488
+ i = i + 1 & 0xff;
489
+ j = j + s[i] & 0xff;
490
+ [s[i], s[j]] = [s[j], s[i]];
491
+ output[k] = data[k] ^ s[s[i] + s[j] & 0xff];
492
+ }
493
+ return output;
494
+ }
495
+
496
+ function randomBytes(length) {
497
+ const bytes = new Uint8Array(length);
498
+ if (globalThis.crypto?.getRandomValues) {
499
+ globalThis.crypto.getRandomValues(bytes);
500
+ } else {
501
+ require('crypto').randomFillSync(bytes);
502
+ }
503
+ return bytes;
504
+ }
505
+
450
506
  function inRange(value, rangeGroup) {
451
507
  if (value < rangeGroup[0]) return false;
452
508
  let startRange = 0;
@@ -545,10 +601,10 @@ class PDFSecurity {
545
601
  }
546
602
  infoStr += `${key}: ${info[key].valueOf()}\n`;
547
603
  }
548
- return wordArrayToBuffer(CryptoJS.MD5(infoStr));
604
+ return Buffer.from(md5Hash(infoStr));
549
605
  }
550
606
  static generateRandomWordArray(bytes) {
551
- return CryptoJS.lib.WordArray.random(bytes);
607
+ return randomBytes(bytes);
552
608
  }
553
609
  static create(document, options = {}) {
554
610
  if (!options.ownerPassword && !options.userPassword) {
@@ -640,8 +696,8 @@ class PDFSecurity {
640
696
  encDict.StrF = 'StdCF';
641
697
  }
642
698
  encDict.R = r;
643
- encDict.O = wordArrayToBuffer(ownerPasswordEntry);
644
- encDict.U = wordArrayToBuffer(userPasswordEntry);
699
+ encDict.O = Buffer.from(ownerPasswordEntry);
700
+ encDict.U = Buffer.from(userPasswordEntry);
645
701
  encDict.P = permissions;
646
702
  }
647
703
  _setupEncryptionV5(encDict, options) {
@@ -651,10 +707,10 @@ class PDFSecurity {
651
707
  const processedOwnerPassword = options.ownerPassword ? processPasswordR5(options.ownerPassword) : processedUserPassword;
652
708
  this.encryptionKey = getEncryptionKeyR5(PDFSecurity.generateRandomWordArray);
653
709
  const userPasswordEntry = getUserPasswordR5(processedUserPassword, PDFSecurity.generateRandomWordArray);
654
- const userKeySalt = CryptoJS.lib.WordArray.create(userPasswordEntry.words.slice(10, 12), 8);
710
+ const userKeySalt = userPasswordEntry.slice(40, 48);
655
711
  const userEncryptionKeyEntry = getUserEncryptionKeyR5(processedUserPassword, userKeySalt, this.encryptionKey);
656
712
  const ownerPasswordEntry = getOwnerPasswordR5(processedOwnerPassword, userPasswordEntry, PDFSecurity.generateRandomWordArray);
657
- const ownerKeySalt = CryptoJS.lib.WordArray.create(ownerPasswordEntry.words.slice(10, 12), 8);
713
+ const ownerKeySalt = ownerPasswordEntry.slice(40, 48);
658
714
  const ownerEncryptionKeyEntry = getOwnerEncryptionKeyR5(processedOwnerPassword, ownerKeySalt, userPasswordEntry, this.encryptionKey);
659
715
  const permsEntry = getEncryptedPermissionsR5(permissions, this.encryptionKey, PDFSecurity.generateRandomWordArray);
660
716
  encDict.V = 5;
@@ -669,36 +725,37 @@ class PDFSecurity {
669
725
  encDict.StmF = 'StdCF';
670
726
  encDict.StrF = 'StdCF';
671
727
  encDict.R = 5;
672
- encDict.O = wordArrayToBuffer(ownerPasswordEntry);
673
- encDict.OE = wordArrayToBuffer(ownerEncryptionKeyEntry);
674
- encDict.U = wordArrayToBuffer(userPasswordEntry);
675
- encDict.UE = wordArrayToBuffer(userEncryptionKeyEntry);
728
+ encDict.O = Buffer.from(ownerPasswordEntry);
729
+ encDict.OE = Buffer.from(ownerEncryptionKeyEntry);
730
+ encDict.U = Buffer.from(userPasswordEntry);
731
+ encDict.UE = Buffer.from(userEncryptionKeyEntry);
676
732
  encDict.P = permissions;
677
- encDict.Perms = wordArrayToBuffer(permsEntry);
733
+ encDict.Perms = Buffer.from(permsEntry);
678
734
  }
679
735
  getEncryptFn(obj, gen) {
680
736
  let digest;
681
737
  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));
738
+ const suffix = new Uint8Array([obj & 0xff, obj >> 8 & 0xff, obj >> 16 & 0xff, gen & 0xff, gen >> 8 & 0xff]);
739
+ digest = utils.concatBytes(this.encryptionKey, suffix);
683
740
  }
684
741
  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);
742
+ let key = md5Hash(digest);
743
+ const keyLen = Math.min(16, this.keyBits / 8 + 5);
744
+ key = key.slice(0, keyLen);
745
+ return buffer => Buffer.from(rc4(new Uint8Array(buffer), key));
688
746
  }
689
747
  let key;
690
748
  if (this.version === 4) {
691
- key = CryptoJS.MD5(digest.concat(CryptoJS.lib.WordArray.create([0x73416c54], 4)));
749
+ const saltMarker = new Uint8Array([0x73, 0x41, 0x6c, 0x54]);
750
+ key = md5Hash(utils.concatBytes(digest, saltMarker));
692
751
  } else {
693
752
  key = this.encryptionKey;
694
753
  }
695
754
  const iv = PDFSecurity.generateRandomWordArray(16);
696
- const options = {
697
- mode: CryptoJS.mode.CBC,
698
- padding: CryptoJS.pad.Pkcs7,
699
- iv
755
+ return buffer => {
756
+ const encrypted = aesCbcEncrypt(new Uint8Array(buffer), key, iv, true);
757
+ return Buffer.from(utils.concatBytes(iv, encrypted));
700
758
  };
701
- return buffer => wordArrayToBuffer(iv.clone().concat(CryptoJS.AES.encrypt(CryptoJS.lib.WordArray.create(buffer), key, options).ciphertext));
702
759
  }
703
760
  end() {
704
761
  this.dictionary.end();
@@ -749,89 +806,97 @@ function getPermissionsR3(permissionObject = {}) {
749
806
  return permissions;
750
807
  }
751
808
  function getUserPasswordR2(encryptionKey) {
752
- return CryptoJS.RC4.encrypt(processPasswordR2R3R4(), encryptionKey).ciphertext;
809
+ return rc4(processPasswordR2R3R4(), encryptionKey);
753
810
  }
754
811
  function getUserPasswordR3R4(documentId, encryptionKey) {
755
- const key = encryptionKey.clone();
756
- let cipher = CryptoJS.MD5(processPasswordR2R3R4().concat(CryptoJS.lib.WordArray.create(documentId)));
812
+ const key = encryptionKey.slice();
813
+ let cipher = md5Hash(utils.concatBytes(processPasswordR2R3R4(), new Uint8Array(documentId)));
757
814
  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);
815
+ const xorKey = new Uint8Array(key.length);
816
+ for (let j = 0; j < key.length; j++) {
817
+ xorKey[j] = encryptionKey[j] ^ i;
761
818
  }
762
- cipher = CryptoJS.RC4.encrypt(cipher, key).ciphertext;
819
+ cipher = rc4(cipher, xorKey);
763
820
  }
764
- return cipher.concat(CryptoJS.lib.WordArray.create(null, 16));
821
+ const result = new Uint8Array(32);
822
+ result.set(cipher);
823
+ return result;
765
824
  }
766
825
  function getOwnerPasswordR2R3R4(r, keyBits, paddedUserPassword, paddedOwnerPassword) {
767
826
  let digest = paddedOwnerPassword;
768
827
  let round = r >= 3 ? 51 : 1;
769
828
  for (let i = 0; i < round; i++) {
770
- digest = CryptoJS.MD5(digest);
829
+ digest = md5Hash(digest);
771
830
  }
772
- const key = digest.clone();
773
- key.sigBytes = keyBits / 8;
831
+ const keyLen = keyBits / 8;
832
+ let key = digest.slice(0, keyLen);
774
833
  let cipher = paddedUserPassword;
775
834
  round = r >= 3 ? 20 : 1;
776
835
  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);
836
+ const xorKey = new Uint8Array(keyLen);
837
+ for (let j = 0; j < keyLen; j++) {
838
+ xorKey[j] = key[j] ^ i;
780
839
  }
781
- cipher = CryptoJS.RC4.encrypt(cipher, key).ciphertext;
840
+ cipher = rc4(cipher, xorKey);
782
841
  }
783
842
  return cipher;
784
843
  }
785
844
  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));
845
+ const permBytes = new Uint8Array([permissions & 0xff, permissions >> 8 & 0xff, permissions >> 16 & 0xff, permissions >> 24 & 0xff]);
846
+ let key = utils.concatBytes(paddedUserPassword, ownerPasswordEntry, permBytes, new Uint8Array(documentId));
787
847
  const round = r >= 3 ? 51 : 1;
848
+ const keyLen = keyBits / 8;
788
849
  for (let i = 0; i < round; i++) {
789
- key = CryptoJS.MD5(key);
790
- key.sigBytes = keyBits / 8;
850
+ key = md5Hash(key);
851
+ key = key.slice(0, keyLen);
791
852
  }
792
853
  return key;
793
854
  }
794
855
  function getUserPasswordR5(processedUserPassword, generateRandomWordArray) {
795
856
  const validationSalt = generateRandomWordArray(8);
796
857
  const keySalt = generateRandomWordArray(8);
797
- return CryptoJS.SHA256(processedUserPassword.clone().concat(validationSalt)).concat(validationSalt).concat(keySalt);
858
+ const hash = sha256Hash(utils.concatBytes(processedUserPassword, validationSalt));
859
+ return utils.concatBytes(hash, validationSalt, keySalt);
798
860
  }
799
861
  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;
862
+ const key = sha256Hash(utils.concatBytes(processedUserPassword, userKeySalt));
863
+ const iv = new Uint8Array(16);
864
+ return aesCbcEncrypt(encryptionKey, key, iv, false);
807
865
  }
808
866
  function getOwnerPasswordR5(processedOwnerPassword, userPasswordEntry, generateRandomWordArray) {
809
867
  const validationSalt = generateRandomWordArray(8);
810
868
  const keySalt = generateRandomWordArray(8);
811
- return CryptoJS.SHA256(processedOwnerPassword.clone().concat(validationSalt).concat(userPasswordEntry)).concat(validationSalt).concat(keySalt);
869
+ const hash = sha256Hash(utils.concatBytes(processedOwnerPassword, validationSalt, userPasswordEntry));
870
+ return utils.concatBytes(hash, validationSalt, keySalt);
812
871
  }
813
872
  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;
873
+ const key = sha256Hash(utils.concatBytes(processedOwnerPassword, ownerKeySalt, userPasswordEntry));
874
+ const iv = new Uint8Array(16);
875
+ return aesCbcEncrypt(encryptionKey, key, iv, false);
821
876
  }
822
877
  function getEncryptionKeyR5(generateRandomWordArray) {
823
878
  return generateRandomWordArray(32);
824
879
  }
825
880
  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;
881
+ const data = new Uint8Array(16);
882
+ data[0] = permissions & 0xff;
883
+ data[1] = permissions >> 8 & 0xff;
884
+ data[2] = permissions >> 16 & 0xff;
885
+ data[3] = permissions >> 24 & 0xff;
886
+ data[4] = 0xff;
887
+ data[5] = 0xff;
888
+ data[6] = 0xff;
889
+ data[7] = 0xff;
890
+ data[8] = 0x54;
891
+ data[9] = 0x61;
892
+ data[10] = 0x64;
893
+ data[11] = 0x62;
894
+ const randomPart = generateRandomWordArray(4);
895
+ data.set(randomPart, 12);
896
+ return aesEcbEncrypt(data, encryptionKey);
832
897
  }
833
898
  function processPasswordR2R3R4(password = '') {
834
- const out = Buffer.alloc(32);
899
+ const out = new Uint8Array(32);
835
900
  const length = password.length;
836
901
  let index = 0;
837
902
  while (index < length && index < 32) {
@@ -846,26 +911,16 @@ function processPasswordR2R3R4(password = '') {
846
911
  out[index] = PASSWORD_PADDING[index - length];
847
912
  index++;
848
913
  }
849
- return CryptoJS.lib.WordArray.create(out);
914
+ return out;
850
915
  }
851
916
  function processPasswordR5(password = '') {
852
917
  password = unescape(encodeURIComponent(saslprep(password)));
853
918
  const length = Math.min(127, password.length);
854
- const out = Buffer.alloc(length);
919
+ const out = new Uint8Array(length);
855
920
  for (let i = 0; i < length; i++) {
856
921
  out[i] = password.charCodeAt(i);
857
922
  }
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);
923
+ return out;
869
924
  }
870
925
  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
926
 
@@ -1473,86 +1528,176 @@ const parameters = {
1473
1528
  Z: 0,
1474
1529
  z: 0
1475
1530
  };
1531
+ const isCommand = function (c) {
1532
+ return c in parameters;
1533
+ };
1534
+ const isWsp = function (c) {
1535
+ const codePoint = c.codePointAt(0);
1536
+ return codePoint === 0x20 || codePoint === 0x9 || codePoint === 0xd || codePoint === 0xa;
1537
+ };
1538
+ const isDigit = function (c) {
1539
+ const codePoint = c.codePointAt(0);
1540
+ if (codePoint == null) {
1541
+ return false;
1542
+ }
1543
+ return 48 <= codePoint && codePoint <= 57;
1544
+ };
1545
+ const readNumber = function (string, cursor) {
1546
+ let i = cursor;
1547
+ let value = '';
1548
+ let state = 'none';
1549
+ for (; i < string.length; i += 1) {
1550
+ const c = string[i];
1551
+ if (c === '+' || c === '-') {
1552
+ if (state === 'none') {
1553
+ state = 'sign';
1554
+ value += c;
1555
+ continue;
1556
+ }
1557
+ if (state === 'e') {
1558
+ state = 'exponent_sign';
1559
+ value += c;
1560
+ continue;
1561
+ }
1562
+ }
1563
+ if (isDigit(c)) {
1564
+ if (state === 'none' || state === 'sign' || state === 'whole') {
1565
+ state = 'whole';
1566
+ value += c;
1567
+ continue;
1568
+ }
1569
+ if (state === 'decimal_point' || state === 'decimal') {
1570
+ state = 'decimal';
1571
+ value += c;
1572
+ continue;
1573
+ }
1574
+ if (state === 'e' || state === 'exponent_sign' || state === 'exponent') {
1575
+ state = 'exponent';
1576
+ value += c;
1577
+ continue;
1578
+ }
1579
+ }
1580
+ if (c === '.') {
1581
+ if (state === 'none' || state === 'sign' || state === 'whole') {
1582
+ state = 'decimal_point';
1583
+ value += c;
1584
+ continue;
1585
+ }
1586
+ }
1587
+ if (c === 'E' || c === 'e') {
1588
+ if (state === 'whole' || state === 'decimal_point' || state === 'decimal') {
1589
+ state = 'e';
1590
+ value += c;
1591
+ continue;
1592
+ }
1593
+ }
1594
+ break;
1595
+ }
1596
+ const number = Number.parseFloat(value);
1597
+ if (Number.isNaN(number)) {
1598
+ return [cursor, null];
1599
+ }
1600
+ return [i - 1, number];
1601
+ };
1476
1602
  const parse = function (path) {
1477
- let cmd;
1478
- const ret = [];
1603
+ const pathData = [];
1604
+ let command = null;
1479
1605
  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;
1606
+ let argsCount = 0;
1607
+ let canHaveComma = false;
1608
+ let hadComma = false;
1609
+ for (let i = 0; i < path.length; i += 1) {
1610
+ const c = path.charAt(i);
1611
+ if (isWsp(c)) {
1612
+ continue;
1613
+ }
1614
+ if (canHaveComma && c === ',') {
1615
+ if (hadComma) {
1616
+ break;
1617
+ }
1618
+ hadComma = true;
1619
+ continue;
1620
+ }
1621
+ if (isCommand(c)) {
1622
+ if (hadComma) {
1623
+ return pathData;
1624
+ }
1625
+ if (command == null) {
1626
+ if (c !== 'M' && c !== 'm') {
1627
+ return pathData;
1489
1628
  }
1490
- ret[ret.length] = {
1491
- cmd,
1629
+ } else {
1630
+ if (args.length !== 0) {
1631
+ return pathData;
1632
+ }
1633
+ }
1634
+ command = c;
1635
+ args = [];
1636
+ argsCount = parameters[command];
1637
+ canHaveComma = false;
1638
+ if (argsCount === 0) {
1639
+ pathData.push({
1640
+ command,
1492
1641
  args
1493
- };
1494
- args = [];
1495
- curArg = '';
1496
- foundDecimal = false;
1642
+ });
1497
1643
  }
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;
1644
+ continue;
1645
+ }
1646
+ if (command == null) {
1647
+ return pathData;
1648
+ }
1649
+ let newCursor = i;
1650
+ let number = null;
1651
+ if (command === 'A' || command === 'a') {
1652
+ const position = args.length;
1653
+ if (position === 0 || position === 1) {
1654
+ if (c !== '+' && c !== '-') {
1655
+ [newCursor, number] = readNumber(path, i);
1656
+ }
1502
1657
  }
1503
- if (args.length === params) {
1504
- ret[ret.length] = {
1505
- cmd,
1506
- args
1507
- };
1508
- args = [+curArg];
1509
- if (cmd === 'M') {
1510
- cmd = 'L';
1658
+ if (position === 2 || position === 5 || position === 6) {
1659
+ [newCursor, number] = readNumber(path, i);
1660
+ }
1661
+ if (position === 3 || position === 4) {
1662
+ if (c === '0') {
1663
+ number = 0;
1511
1664
  }
1512
- if (cmd === 'm') {
1513
- cmd = 'l';
1665
+ if (c === '1') {
1666
+ number = 1;
1514
1667
  }
1515
- } else {
1516
- args[args.length] = +curArg;
1517
1668
  }
1518
- foundDecimal = c === '.';
1519
- curArg = ['-', '.'].includes(c) ? c : '';
1520
1669
  } 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,
1670
+ [newCursor, number] = readNumber(path, i);
1671
+ }
1672
+ if (number == null) {
1673
+ return pathData;
1674
+ }
1675
+ args.push(number);
1676
+ canHaveComma = true;
1677
+ hadComma = false;
1678
+ i = newCursor;
1679
+ if (args.length === argsCount) {
1680
+ pathData.push({
1681
+ command,
1531
1682
  args
1532
- };
1533
- args = [+curArg];
1534
- if (cmd === 'M') {
1535
- cmd = 'L';
1683
+ });
1684
+ if (command === 'M') {
1685
+ command = 'L';
1536
1686
  }
1537
- if (cmd === 'm') {
1538
- cmd = 'l';
1687
+ if (command === 'm') {
1688
+ command = 'l';
1539
1689
  }
1540
- } else {
1541
- args[args.length] = +curArg;
1690
+ args = [];
1542
1691
  }
1543
1692
  }
1544
- ret[ret.length] = {
1545
- cmd,
1546
- args
1547
- };
1548
- return ret;
1693
+ return pathData;
1549
1694
  };
1550
1695
  const apply = function (commands, doc) {
1551
1696
  cx = cy = px = py = sx = sy = 0;
1552
1697
  for (let i = 0; i < commands.length; i++) {
1553
1698
  const c = commands[i];
1554
- if (typeof runners[c.cmd] === 'function') {
1555
- runners[c.cmd](doc, c.args);
1699
+ if (typeof runners[c.command] === 'function') {
1700
+ runners[c.command](doc, c.args);
1556
1701
  }
1557
1702
  }
1558
1703
  };
@@ -2584,7 +2729,7 @@ begincmap
2584
2729
  1 begincodespacerange
2585
2730
  <0000><ffff>
2586
2731
  endcodespacerange
2587
- 1 beginbfrange
2732
+ ${ranges.length} beginbfrange
2588
2733
  ${ranges.join('\n')}
2589
2734
  endbfrange
2590
2735
  endcmap
@@ -2763,6 +2908,7 @@ class LineWrapper extends events.EventEmitter {
2763
2908
  this.document = document;
2764
2909
  this.horizontalScaling = options.horizontalScaling || 100;
2765
2910
  this.indent = (options.indent || 0) * this.horizontalScaling / 100;
2911
+ this.indentAllLines = options.indentAllLines || false;
2766
2912
  this.characterSpacing = (options.characterSpacing || 0) * this.horizontalScaling / 100;
2767
2913
  this.wordSpacing = (options.wordSpacing === 0) * this.horizontalScaling / 100;
2768
2914
  this.columns = options.columns || 1;
@@ -3004,7 +3150,13 @@ class LineWrapper extends events.EventEmitter {
3004
3150
  this.column = 1;
3005
3151
  this.startY = this.document.page.margins.top;
3006
3152
  this.maxY = this.document.page.maxY();
3007
- this.document.x = this.startX;
3153
+ if (this.indentAllLines) {
3154
+ const indent = this.continuedX || this.indent;
3155
+ this.document.x += indent;
3156
+ this.lineWidth -= indent;
3157
+ } else {
3158
+ this.document.x = this.startX;
3159
+ }
3008
3160
  if (this.document._fillColor) {
3009
3161
  this.document.fillColor(...this.document._fillColor);
3010
3162
  }
@@ -3408,7 +3560,11 @@ var TextMixin = {
3408
3560
  }
3409
3561
  const renderedWidth = options.textWidth + wordSpacing * (options.wordCount - 1) + characterSpacing * (text.length - 1);
3410
3562
  if (options.link != null) {
3411
- this.link(x, y, renderedWidth, this.currentLineHeight(), options.link);
3563
+ const linkOptions = {};
3564
+ if (this._currentStructureElement && this._currentStructureElement.dictionary.data.S === 'Link') {
3565
+ linkOptions.structParent = this._currentStructureElement;
3566
+ }
3567
+ this.link(x, y, renderedWidth, this.currentLineHeight(), options.link, linkOptions);
3412
3568
  }
3413
3569
  if (options.goTo != null) {
3414
3570
  this.goTo(x, y, renderedWidth, this.currentLineHeight(), options.goTo);
@@ -3537,6 +3693,47 @@ var TextMixin = {
3537
3693
  }
3538
3694
  };
3539
3695
 
3696
+ const parseExifOrientation = data => {
3697
+ if (!data || data.length < 20) return null;
3698
+ let pos = 2;
3699
+ while (pos < data.length - 4) {
3700
+ while (pos < data.length && data[pos] !== 0xff) pos++;
3701
+ if (pos >= data.length - 4) return null;
3702
+ const marker = data.readUInt16BE(pos);
3703
+ pos += 2;
3704
+ if (marker === 0xffda) return null;
3705
+ if (marker >= 0xffd0 && marker <= 0xffd9 || marker === 0xff01) continue;
3706
+ if (pos + 2 > data.length) return null;
3707
+ const segmentLength = data.readUInt16BE(pos);
3708
+ if (marker === 0xffe1 && pos + 8 <= data.length) {
3709
+ const exifHeader = data.subarray(pos + 2, pos + 8).toString('binary');
3710
+ if (exifHeader === 'Exif\x00\x00') {
3711
+ const tiffStart = pos + 8;
3712
+ if (tiffStart + 8 > data.length) return null;
3713
+ const byteOrder = data.subarray(tiffStart, tiffStart + 2).toString('ascii');
3714
+ const isLittleEndian = byteOrder === 'II';
3715
+ if (!isLittleEndian && byteOrder !== 'MM') return null;
3716
+ const read16 = isLittleEndian ? o => data.readUInt16LE(o) : o => data.readUInt16BE(o);
3717
+ const read32 = isLittleEndian ? o => data.readUInt32LE(o) : o => data.readUInt32BE(o);
3718
+ if (read16(tiffStart + 2) !== 42) return null;
3719
+ const ifdPos = tiffStart + read32(tiffStart + 4);
3720
+ if (ifdPos + 2 > data.length) return null;
3721
+ const entryCount = read16(ifdPos);
3722
+ for (let i = 0; i < entryCount; i++) {
3723
+ const entryPos = ifdPos + 2 + i * 12;
3724
+ if (entryPos + 12 > data.length) return null;
3725
+ if (read16(entryPos) === 0x0112) {
3726
+ const value = read16(entryPos + 8);
3727
+ return value >= 1 && value <= 8 ? value : null;
3728
+ }
3729
+ }
3730
+ return null;
3731
+ }
3732
+ }
3733
+ pos += segmentLength;
3734
+ }
3735
+ return null;
3736
+ };
3540
3737
  const MARKERS = [0xffc0, 0xffc1, 0xffc2, 0xffc3, 0xffc5, 0xffc6, 0xffc7, 0xffc8, 0xffc9, 0xffca, 0xffcb, 0xffcc, 0xffcd, 0xffce, 0xffcf];
3541
3738
  const COLOR_SPACE_MAP = {
3542
3739
  1: 'DeviceGray',
@@ -3551,9 +3748,11 @@ class JPEG {
3551
3748
  if (this.data.readUInt16BE(0) !== 0xffd8) {
3552
3749
  throw 'SOI not found in JPEG';
3553
3750
  }
3554
- this.orientation = exif.fromBuffer(this.data).Orientation || 1;
3751
+ this.orientation = parseExifOrientation(this.data) || 1;
3555
3752
  let pos = 2;
3556
3753
  while (pos < this.data.length) {
3754
+ while (pos < this.data.length && this.data[pos] !== 0xff) pos++;
3755
+ if (pos >= this.data.length) break;
3557
3756
  marker = this.data.readUInt16BE(pos);
3558
3757
  pos += 2;
3559
3758
  if (MARKERS.includes(marker)) {
@@ -3705,12 +3904,16 @@ class PNGImage {
3705
3904
  }
3706
3905
  loadIndexedAlphaChannel() {
3707
3906
  const transparency = this.image.transparency.indexed;
3907
+ const isInterlaced = this.image.interlaceMethod === 1;
3708
3908
  return this.image.decodePixels(pixels => {
3709
3909
  const alphaChannel = Buffer.alloc(this.width * this.height);
3710
3910
  let i = 0;
3711
3911
  for (let j = 0, end = pixels.length; j < end; j++) {
3712
3912
  alphaChannel[i++] = transparency[pixels[j]];
3713
3913
  }
3914
+ if (isInterlaced) {
3915
+ this.imgData = zlib.deflateSync(Buffer.from(pixels));
3916
+ }
3714
3917
  this.alphaChannel = zlib.deflateSync(alphaChannel);
3715
3918
  return this.finalize();
3716
3919
  });
@@ -3939,6 +4142,12 @@ var ImagesMixin = {
3939
4142
  }
3940
4143
  };
3941
4144
 
4145
+ class PDFAnnotationReference {
4146
+ constructor(annotationRef) {
4147
+ this.annotationRef = annotationRef;
4148
+ }
4149
+ }
4150
+
3942
4151
  var AnnotationsMixin = {
3943
4152
  annotate(x, y, w, h, options) {
3944
4153
  options.Type = 'Annot';
@@ -3956,12 +4165,18 @@ var AnnotationsMixin = {
3956
4165
  if (typeof options.Dest === 'string') {
3957
4166
  options.Dest = new String(options.Dest);
3958
4167
  }
4168
+ const structParent = options.structParent;
4169
+ delete options.structParent;
3959
4170
  for (let key in options) {
3960
4171
  const val = options[key];
3961
4172
  options[key[0].toUpperCase() + key.slice(1)] = val;
3962
4173
  }
3963
4174
  const ref = this.ref(options);
3964
4175
  this.page.annotations.push(ref);
4176
+ if (structParent && typeof structParent.add === 'function') {
4177
+ const annotRef = new PDFAnnotationReference(ref);
4178
+ structParent.add(annotRef);
4179
+ }
3965
4180
  ref.end();
3966
4181
  return this;
3967
4182
  },
@@ -4005,6 +4220,9 @@ var AnnotationsMixin = {
4005
4220
  });
4006
4221
  options.A.end();
4007
4222
  }
4223
+ if (options.structParent && !options.Contents) {
4224
+ options.Contents = new String('');
4225
+ }
4008
4226
  return this.annotate(x, y, w, h, options);
4009
4227
  },
4010
4228
  _markup(x, y, w, h, options = {}) {
@@ -4076,15 +4294,26 @@ var AnnotationsMixin = {
4076
4294
  }
4077
4295
  };
4078
4296
 
4297
+ const DEFAULT_OPTIONS = {
4298
+ top: 0,
4299
+ left: 0,
4300
+ zoom: 0,
4301
+ fit: true,
4302
+ pageNumber: null,
4303
+ expanded: false
4304
+ };
4079
4305
  class PDFOutline {
4080
- constructor(document, parent, title, dest, options = {
4081
- expanded: false
4082
- }) {
4306
+ constructor(document, parent, title, dest, options = DEFAULT_OPTIONS) {
4083
4307
  this.document = document;
4084
4308
  this.options = options;
4085
4309
  this.outlineData = {};
4086
4310
  if (dest !== null) {
4087
- this.outlineData['Dest'] = [dest.dictionary, 'Fit'];
4311
+ const destWidth = dest.data.MediaBox[2];
4312
+ const destHeight = dest.data.MediaBox[3];
4313
+ const top = destHeight - (options.top || 0);
4314
+ const left = destWidth - (options.left || 0);
4315
+ const zoom = options.zoom || 0;
4316
+ this.outlineData['Dest'] = options.fit ? [dest, 'Fit'] : [dest, 'XYZ', left, top, zoom];
4088
4317
  }
4089
4318
  if (parent !== null) {
4090
4319
  this.outlineData['Parent'] = parent;
@@ -4095,10 +4324,10 @@ class PDFOutline {
4095
4324
  this.dictionary = this.document.ref(this.outlineData);
4096
4325
  this.children = [];
4097
4326
  }
4098
- addItem(title, options = {
4099
- expanded: false
4100
- }) {
4101
- const result = new PDFOutline(this.document, this.dictionary, title, this.document.page, options);
4327
+ addItem(title, options = DEFAULT_OPTIONS) {
4328
+ const pages = this.document._root.data.Pages.data.Kids;
4329
+ const dest = options.pageNumber != null ? pages[options.pageNumber] : this.document.page.dictionary;
4330
+ const result = new PDFOutline(this.document, this.dictionary, title, dest, options);
4102
4331
  this.children.push(result);
4103
4332
  return result;
4104
4333
  }
@@ -4134,7 +4363,7 @@ var OutlineMixin = {
4134
4363
  this.outline.endOutline();
4135
4364
  if (this.outline.children.length > 0) {
4136
4365
  this._root.data.Outlines = this.outline.dictionary;
4137
- return this._root.data.PageMode = 'UseOutlines';
4366
+ return this._root.data.PageMode = this._root.data.PageMode || 'UseOutlines';
4138
4367
  }
4139
4368
  }
4140
4369
  };
@@ -4205,6 +4434,9 @@ class PDFStructureElement {
4205
4434
  if (child instanceof PDFStructureContent) {
4206
4435
  this._addContentToParentTree(child);
4207
4436
  }
4437
+ if (child instanceof PDFAnnotationReference) {
4438
+ this._addAnnotationToParentTree(child.annotationRef);
4439
+ }
4208
4440
  if (typeof child === 'function' && this._attached) {
4209
4441
  child = this._contentForClosure(child);
4210
4442
  }
@@ -4220,6 +4452,12 @@ class PDFStructureElement {
4220
4452
  pageStructParents[mcid] = this.dictionary;
4221
4453
  });
4222
4454
  }
4455
+ _addAnnotationToParentTree(annotRef) {
4456
+ const parentTreeKey = this.document.createStructParentTreeNextKey();
4457
+ annotRef.data.StructParent = parentTreeKey;
4458
+ const parentTree = this.document.getStructParentTree();
4459
+ parentTree.add(parentTreeKey, this.dictionary);
4460
+ }
4223
4461
  setParent(parentRef) {
4224
4462
  if (this.dictionary.data.P) {
4225
4463
  throw new Error(`Structure element added to more than one parent`);
@@ -4251,11 +4489,17 @@ class PDFStructureElement {
4251
4489
  this._flush();
4252
4490
  }
4253
4491
  _isValidChild(child) {
4254
- return child instanceof PDFStructureElement || child instanceof PDFStructureContent || typeof child === 'function';
4492
+ return child instanceof PDFStructureElement || child instanceof PDFStructureContent || child instanceof PDFAnnotationReference || typeof child === 'function';
4255
4493
  }
4256
4494
  _contentForClosure(closure) {
4257
4495
  const content = this.document.markStructureContent(this.dictionary.data.S);
4496
+ const prevStructElement = this.document._currentStructureElement;
4497
+ this.document._currentStructureElement = this;
4498
+ const wasEnded = this._ended;
4499
+ this._ended = false;
4258
4500
  closure();
4501
+ this._ended = wasEnded;
4502
+ this.document._currentStructureElement = prevStructElement;
4259
4503
  this.document.endMarkedContent();
4260
4504
  this._addContentToParentTree(content);
4261
4505
  return content;
@@ -4308,6 +4552,15 @@ class PDFStructureElement {
4308
4552
  }
4309
4553
  });
4310
4554
  }
4555
+ if (child instanceof PDFAnnotationReference) {
4556
+ const pageRef = this.document.page.dictionary;
4557
+ const objr = {
4558
+ Type: 'OBJR',
4559
+ Obj: child.annotationRef,
4560
+ Pg: pageRef
4561
+ };
4562
+ this.dictionary.data.K.push(objr);
4563
+ }
4311
4564
  }
4312
4565
  }
4313
4566
 
@@ -4401,6 +4654,13 @@ var MarkingsMixin = {
4401
4654
  endMarkedContent() {
4402
4655
  this.page.markings.pop();
4403
4656
  this.addContent('EMC');
4657
+ if (this._textOptions) {
4658
+ delete this._textOptions.link;
4659
+ delete this._textOptions.goTo;
4660
+ delete this._textOptions.destination;
4661
+ delete this._textOptions.underline;
4662
+ delete this._textOptions.strike;
4663
+ }
4404
4664
  return this;
4405
4665
  },
4406
4666
  struct(type, options = {}, children = null) {
@@ -4847,7 +5107,7 @@ var AttachmentsMixin = {
4847
5107
  if (options.type) {
4848
5108
  refBody.Subtype = options.type.replace('/', '#2F');
4849
5109
  }
4850
- const checksum = CryptoJS.MD5(CryptoJS.lib.WordArray.create(new Uint8Array(data)));
5110
+ const checksum = md5Hex(new Uint8Array(data));
4851
5111
  refBody.Params.CheckSum = new String(checksum);
4852
5112
  refBody.Params.Size = data.byteLength;
4853
5113
  let ref;
@@ -5490,7 +5750,7 @@ function renderRow(row, rowIndex) {
5490
5750
  function renderCell(cell, rowStruct) {
5491
5751
  const cellRenderer = () => {
5492
5752
  if (cell.backgroundColor != null) {
5493
- this.document.save().rect(cell.x, cell.y, cell.width, cell.height).fill(cell.backgroundColor).restore();
5753
+ this.document.save().fillColor(cell.backgroundColor).rect(cell.x, cell.y, cell.width, cell.height).fill().restore();
5494
5754
  }
5495
5755
  renderBorder.call(this, cell.border, cell.borderColor, cell.x, cell.y, cell.width, cell.height);
5496
5756
  if (cell.debug) {
@@ -5551,20 +5811,20 @@ function renderBorder(border, borderColor, x, y, width, height, mask) {
5551
5811
  const doc = this.document;
5552
5812
  if ([border.right, border.bottom, border.left].every(val => val === border.top)) {
5553
5813
  if (border.top > 0) {
5554
- doc.save().lineWidth(border.top).rect(x, y, width, height).stroke(borderColor.top).restore();
5814
+ doc.save().lineWidth(border.top).strokeColor(borderColor.top).rect(x, y, width, height).stroke().restore();
5555
5815
  }
5556
5816
  } else {
5557
5817
  if (border.top > 0) {
5558
- doc.save().lineWidth(border.top).moveTo(x, y).lineTo(x + width, y).stroke(borderColor.top).restore();
5818
+ doc.save().lineWidth(border.top).moveTo(x, y).strokeColor(borderColor.top).lineTo(x + width, y).stroke().restore();
5559
5819
  }
5560
5820
  if (border.right > 0) {
5561
- doc.save().lineWidth(border.right).moveTo(x + width, y).lineTo(x + width, y + height).stroke(borderColor.right).restore();
5821
+ doc.save().lineWidth(border.right).moveTo(x + width, y).strokeColor(borderColor.right).lineTo(x + width, y + height).stroke().restore();
5562
5822
  }
5563
5823
  if (border.bottom > 0) {
5564
- doc.save().lineWidth(border.bottom).moveTo(x + width, y + height).lineTo(x, y + height).stroke(borderColor.bottom).restore();
5824
+ doc.save().lineWidth(border.bottom).moveTo(x + width, y + height).strokeColor(borderColor.bottom).lineTo(x, y + height).stroke().restore();
5565
5825
  }
5566
5826
  if (border.left > 0) {
5567
- doc.save().lineWidth(border.left).moveTo(x, y + height).lineTo(x, y).stroke(borderColor.left).restore();
5827
+ doc.save().lineWidth(border.left).moveTo(x, y + height).strokeColor(borderColor.left).lineTo(x, y).stroke().restore();
5568
5828
  }
5569
5829
  }
5570
5830
  }
@@ -5771,6 +6031,10 @@ class PDFDocument extends stream.Readable {
5771
6031
  if (this.options.lang) {
5772
6032
  this._root.data.Lang = new String(this.options.lang);
5773
6033
  }
6034
+ if (this.options.pageLayout) {
6035
+ const layout = this.options.pageLayout;
6036
+ this._root.data.PageLayout = layout.charAt(0).toUpperCase() + layout.slice(1);
6037
+ }
5774
6038
  this.page = null;
5775
6039
  this.initMetadata();
5776
6040
  this.initColor();